Merge pull request #1041 from rboyer/api-fix-session-renew

Fix Session.Renew to care about 404s properly
This commit is contained in:
James Phillips 2015-09-25 13:15:54 -07:00
commit c74355fec0
2 changed files with 130 additions and 5 deletions

View File

@ -1,6 +1,7 @@
package api package api
import ( import (
"errors"
"fmt" "fmt"
"time" "time"
) )
@ -16,6 +17,8 @@ const (
SessionBehaviorDelete = "delete" SessionBehaviorDelete = "delete"
) )
var ErrSessionExpired = errors.New("session expired")
// SessionEntry represents a session in consul // SessionEntry represents a session in consul
type SessionEntry struct { type SessionEntry struct {
CreateIndex uint64 CreateIndex uint64
@ -113,11 +116,26 @@ func (s *Session) Destroy(id string, q *WriteOptions) (*WriteMeta, error) {
// Renew renews the TTL on a given session // Renew renews the TTL on a given session
func (s *Session) Renew(id string, q *WriteOptions) (*SessionEntry, *WriteMeta, error) { func (s *Session) Renew(id string, q *WriteOptions) (*SessionEntry, *WriteMeta, error) {
var entries []*SessionEntry r := s.c.newRequest("PUT", "/v1/session/renew/"+id)
wm, err := s.c.write("/v1/session/renew/"+id, nil, &entries, q) r.setWriteOptions(q)
rtt, resp, err := s.c.doRequest(r)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
defer resp.Body.Close()
wm := &WriteMeta{RequestTime: rtt}
if resp.StatusCode == 404 {
return nil, wm, nil
} else if resp.StatusCode != 200 {
return nil, nil, fmt.Errorf("Unexpected response code: %d", resp.StatusCode)
}
var entries []*SessionEntry
if err := decodeBody(resp, &entries); err != nil {
return nil, nil, fmt.Errorf("Failed to read response: %v", err)
}
if len(entries) > 0 { if len(entries) > 0 {
return entries[0], wm, nil return entries[0], wm, nil
} }
@ -149,9 +167,7 @@ func (s *Session) RenewPeriodic(initialTTL string, id string, q *WriteOptions, d
continue continue
} }
if entry == nil { if entry == nil {
waitDur = time.Second return ErrSessionExpired
lastErr = fmt.Errorf("No SessionEntry returned")
continue
} }
// Handle the server updating the TTL // Handle the server updating the TTL

View File

@ -2,6 +2,7 @@ package api
import ( import (
"testing" "testing"
"time"
) )
func TestSession_CreateDestroy(t *testing.T) { func TestSession_CreateDestroy(t *testing.T) {
@ -85,6 +86,114 @@ func TestSession_CreateRenewDestroy(t *testing.T) {
} }
} }
func TestSession_CreateRenewDestroyRenew(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
session := c.Session()
entry := &SessionEntry{
Behavior: SessionBehaviorDelete,
TTL: "500s", // disable ttl
}
id, meta, err := session.Create(entry, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if meta.RequestTime == 0 {
t.Fatalf("bad: %v", meta)
}
if id == "" {
t.Fatalf("invalid: %v", id)
}
// Extend right after create. Everything should be fine.
entry, _, err = session.Renew(id, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if entry == nil {
t.Fatal("session unexpectedly vanished")
}
// Simulate TTL loss by manually destroying the session.
meta, err = session.Destroy(id, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if meta.RequestTime == 0 {
t.Fatalf("bad: %v", meta)
}
// Extend right after delete. The 404 should proxy as a nil.
entry, _, err = session.Renew(id, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if entry != nil {
t.Fatal("session still exists")
}
}
func TestSession_CreateDestroyRenewPeriodic(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
session := c.Session()
entry := &SessionEntry{
Behavior: SessionBehaviorDelete,
TTL: "500s", // disable ttl
}
id, meta, err := session.Create(entry, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if meta.RequestTime == 0 {
t.Fatalf("bad: %v", meta)
}
if id == "" {
t.Fatalf("invalid: %v", id)
}
// This only tests Create/Destroy/RenewPeriodic to avoid the more
// difficult case of testing all of the timing code.
// Simulate TTL loss by manually destroying the session.
meta, err = session.Destroy(id, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if meta.RequestTime == 0 {
t.Fatalf("bad: %v", meta)
}
// Extend right after delete. The 404 should terminate the loop quickly and return ErrSessionExpired.
errCh := make(chan error, 1)
doneCh := make(chan struct{})
go func() { errCh <- session.RenewPeriodic("1s", id, nil, doneCh) }()
defer close(doneCh)
select {
case <-time.After(1 * time.Second):
t.Fatal("timedout: missing session did not terminate renewal loop")
case err = <-errCh:
if err != ErrSessionExpired {
t.Fatalf("err: %v", err)
}
}
}
func TestSession_Info(t *testing.T) { func TestSession_Info(t *testing.T) {
t.Parallel() t.Parallel()
c, s := makeClient(t) c, s := makeClient(t)