diff --git a/Makefile b/Makefile index 8610c77a..2a87d5fc 100644 --- a/Makefile +++ b/Makefile @@ -3,9 +3,7 @@ PROJECT := github.com/juju/utils/v3 .PHONY: check-licence check-go check check: check-licence check-go - # TODO - testing this way results in a go.sum dep error - # go test $(PROJECT)/... - go test ./... + go test $(PROJECT)/... check-licence: @(grep -rFl "Licensed under the LGPLv3" .;\ diff --git a/debugstatus/handler.go b/debugstatus/handler.go deleted file mode 100644 index 2ef7bdf3..00000000 --- a/debugstatus/handler.go +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2015 Canonical Ltd. -// Licensed under the LGPLv3, see LICENCE file for details. - -package debugstatus - -import ( - "net/http" - - "golang.org/x/net/trace" - "gopkg.in/errgo.v1" - - pprof "github.com/juju/httpprof" - "golang.org/x/net/context" - "gopkg.in/httprequest.v1" -) - -// Version describes the current version of the code being run. -type Version struct { - GitCommit string - Version string -} - -// Handler implements a type that can be used with httprequest.Handlers -// to serve a standard set of /debug endpoints, including -// the version of the system, its current health status -// the runtime profiling information. -type Handler struct { - // Check will be called to obtain the current health of the - // system. It should return a map as returned from the - // Check function. If this is nil, an empty result will - // always be returned from /debug/status. - Check func(context.Context) map[string]CheckResult - - // Version should hold the current version - // of the binary running the server, served - // from the /debug/info endpoint. - Version Version - - // CheckPprofAllowed will be used to check whether the - // given pprof request should be allowed. - // It should return an error if not, which will not be masked. - // If this is nil, no access will be allowed to any - // of the endpoints under /debug/pprof - the - // error returned will be ErrNoPprofConfigured. - CheckPprofAllowed func(req *http.Request) error - - // CheckTraceAllowed will be used to check whether the given - // trace request should be allowed. It should return an error if - // not, which will not be masked. If this is nil, no access will - // be allowed to either /debug/events or /debug/requests - the - // error returned will be ErrNoTraceConfigured. If access is - // allowed, the sensitive value specifies whether sensitive trace - // events will be shown. - CheckTraceAllowed func(req *http.Request) (sensitive bool, err error) -} - -// DebugStatusRequest describes the /debug/status endpoint. -type DebugStatusRequest struct { - httprequest.Route `httprequest:"GET /debug/status"` -} - -// DebugStatus returns the current status of the server. -func (h *Handler) DebugStatus(p httprequest.Params, _ *DebugStatusRequest) (map[string]CheckResult, error) { - if h.Check == nil { - return map[string]CheckResult{}, nil - } - return h.Check(p.Context), nil -} - -// DebugInfoRequest describes the /debug/info endpoint. -type DebugInfoRequest struct { - httprequest.Route `httprequest:"GET /debug/info"` -} - -// DebugInfo returns version information on the current server. -func (h *Handler) DebugInfo(*DebugInfoRequest) (Version, error) { - return h.Version, nil -} - -// DebugPprofRequest describes the /debug/pprof/ endpoint. -type DebugPprofRequest struct { - httprequest.Route `httprequest:"GET /debug/pprof/"` -} - -// DebugPprof serves index information on the available pprof endpoints. -func (h *Handler) DebugPprof(p httprequest.Params, _ *DebugPprofRequest) error { - if err := h.checkPprofAllowed(p.Request); err != nil { - return err - } - pprof.Index(p.Response, p.Request) - return nil -} - -// DebugPprofEndpointsRequest describes the endpoints under /debug/prof. -type DebugPprofEndpointsRequest struct { - httprequest.Route `httprequest:"GET /debug/pprof/:name"` - Name string `httprequest:"name,path"` -} - -// DebugPprofEndpoints serves all the endpoints under DebugPprof. -func (h *Handler) DebugPprofEndpoints(p httprequest.Params, r *DebugPprofEndpointsRequest) error { - if err := h.checkPprofAllowed(p.Request); err != nil { - return err - } - switch r.Name { - case "cmdline": - pprof.Cmdline(p.Response, p.Request) - case "profile": - pprof.Profile(p.Response, p.Request) - case "symbol": - pprof.Symbol(p.Response, p.Request) - default: - pprof.Handler(r.Name).ServeHTTP(p.Response, p.Request) - } - return nil -} - -// ErrNoPprofConfigured is the error returned on access -// to endpoints when Handler.CheckPprofAllowed is nil. -var ErrNoPprofConfigured = errgo.New("no pprof access configured") - -// checkPprofAllowed is used instead of h.CheckPprofAllowed -// so that we don't panic if that is nil. -func (h *Handler) checkPprofAllowed(req *http.Request) error { - if h.CheckPprofAllowed == nil { - return ErrNoPprofConfigured - } - return h.CheckPprofAllowed(req) -} - -// DebugEventsRequest describes the /debug/events endpoint. -type DebugEventsRequest struct { - httprequest.Route `httprequest:"GET /debug/events"` -} - -// DebugEvents serves the /debug/events endpoint. -func (h *Handler) DebugEvents(p httprequest.Params, r *DebugEventsRequest) error { - sensitive, err := h.checkTraceAllowed(p.Request) - if err != nil { - return errgo.Mask(err, errgo.Any) - } - trace.RenderEvents(p.Response, p.Request, sensitive) - return nil -} - -// DebugRequestsRequest describes the /debug/requests endpoint. -type DebugRequestsRequest struct { - httprequest.Route `httprequest:"GET /debug/requests"` -} - -// DebugRequests serves the /debug/requests endpoint. -func (h *Handler) DebugRequests(p httprequest.Params, r *DebugRequestsRequest) error { - sensitive, err := h.checkTraceAllowed(p.Request) - if err != nil { - return errgo.Mask(err, errgo.Any) - } - trace.Render(p.Response, p.Request, sensitive) - return nil -} - -// ErrNoTraceConfigured is the error returned on access -// to endpoints when Handler.CheckTraceAllowed is nil. -var ErrNoTraceConfigured = errgo.New("no trace access configured") - -// checkTraceAllowed is used instead of h.CheckTraceAllowed -// so that we don't panic if that is nil. -func (h *Handler) checkTraceAllowed(req *http.Request) (bool, error) { - if h.CheckTraceAllowed == nil { - return false, ErrNoTraceConfigured - } - return h.CheckTraceAllowed(req) -} diff --git a/debugstatus/handler_test.go b/debugstatus/handler_test.go deleted file mode 100644 index 9bbaa28e..00000000 --- a/debugstatus/handler_test.go +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2015 Canonical Ltd. -// Licensed under the LGPLv3, see LICENCE file for details. - -package debugstatus_test - -import ( - "encoding/json" - "net/http" - - jc "github.com/juju/testing/checkers" - "github.com/juju/testing/httptesting" - "github.com/juju/utils/v3/debugstatus" - "github.com/julienschmidt/httprouter" - "golang.org/x/net/context" - gc "gopkg.in/check.v1" - "gopkg.in/errgo.v1" - - "gopkg.in/httprequest.v1" -) - -var reqServer = httprequest.Server{ - ErrorMapper: func(ctx context.Context, err error) (httpStatus int, errorBody interface{}) { - return http.StatusInternalServerError, httprequest.RemoteError{ - Message: err.Error(), - } - }, -} - -type handlerSuite struct { -} - -var _ = gc.Suite(&handlerSuite{}) - -var errUnauthorized = errgo.New("you shall not pass!") - -func newHTTPHandler(h *debugstatus.Handler) http.Handler { - errMapper := func(ctx context.Context, err error) (httpStatus int, errorBody interface{}) { - code, status := "", http.StatusInternalServerError - switch errgo.Cause(err) { - case errUnauthorized: - code, status = "unauthorized", http.StatusUnauthorized - case debugstatus.ErrNoPprofConfigured: - code, status = "forbidden", http.StatusForbidden - case debugstatus.ErrNoTraceConfigured: - code, status = "forbidden", http.StatusForbidden - } - return status, httprequest.RemoteError{ - Code: code, - Message: err.Error(), - } - } - srv := httprequest.Server{ - ErrorMapper: errMapper, - } - - handlers := srv.Handlers(func(p httprequest.Params) (*debugstatus.Handler, context.Context, error) { - return h, p.Context, nil - }) - r := httprouter.New() - for _, h := range handlers { - r.Handle(h.Method, h.Path, h.Handle) - } - return r -} - -func (s *handlerSuite) TestServeDebugStatus(c *gc.C) { - httpHandler := newHTTPHandler(&debugstatus.Handler{ - Check: func(ctx context.Context) map[string]debugstatus.CheckResult { - return debugstatus.Check(ctx, debugstatus.ServerStartTime) - }, - }) - httptesting.AssertJSONCall(c, httptesting.JSONCallParams{ - Handler: httpHandler, - URL: "/debug/status", - ExpectBody: httptesting.BodyAsserter(func(c *gc.C, body json.RawMessage) { - var result map[string]debugstatus.CheckResult - err := json.Unmarshal(body, &result) - c.Assert(err, gc.IsNil) - for k, v := range result { - v.Duration = 0 - result[k] = v - } - c.Assert(result, jc.DeepEquals, map[string]debugstatus.CheckResult{ - "server_started": { - Name: "Server started", - Value: debugstatus.StartTime.String(), - Passed: true, - }, - }) - }), - }) -} - -func (s *handlerSuite) TestServeDebugStatusWithNilCheck(c *gc.C) { - httpHandler := newHTTPHandler(&debugstatus.Handler{}) - httptesting.AssertJSONCall(c, httptesting.JSONCallParams{ - Handler: httpHandler, - URL: "/debug/status", - ExpectBody: map[string]debugstatus.CheckResult{}, - }) -} - -func (s *handlerSuite) TestServeDebugInfo(c *gc.C) { - version := debugstatus.Version{ - GitCommit: "some-git-status", - Version: "a-version", - } - httpHandler := newHTTPHandler(&debugstatus.Handler{ - Version: version, - }) - httptesting.AssertJSONCall(c, httptesting.JSONCallParams{ - Handler: httpHandler, - URL: "/debug/info", - ExpectStatus: http.StatusOK, - ExpectBody: version, - }) -} - -var debugPprofPaths = []string{ - "/debug/pprof/", - "/debug/pprof/cmdline", - "/debug/pprof/profile?seconds=1", - "/debug/pprof/symbol", - "/debug/pprof/goroutine", -} - -func (s *handlerSuite) TestServeDebugPprof(c *gc.C) { - httpHandler := newHTTPHandler(&debugstatus.Handler{ - CheckPprofAllowed: func(req *http.Request) error { - if req.Header.Get("Authorization") == "" { - return errUnauthorized - } - return nil - }, - }) - authHeader := make(http.Header) - authHeader.Set("Authorization", "let me in") - for i, path := range debugPprofPaths { - c.Logf("%d. %s", i, path) - httptesting.AssertJSONCall(c, httptesting.JSONCallParams{ - Handler: httpHandler, - URL: path, - ExpectStatus: http.StatusUnauthorized, - ExpectBody: httprequest.RemoteError{ - Code: "unauthorized", - Message: "you shall not pass!", - }, - }) - rr := httptesting.DoRequest(c, httptesting.DoRequestParams{ - Handler: httpHandler, - URL: path, - Header: authHeader, - }) - c.Assert(rr.Code, gc.Equals, http.StatusOK) - } -} - -func (s *handlerSuite) TestDebugPprofForbiddenWhenNotConfigured(c *gc.C) { - httpHandler := newHTTPHandler(&debugstatus.Handler{}) - httptesting.AssertJSONCall(c, httptesting.JSONCallParams{ - Handler: httpHandler, - URL: "/debug/pprof/", - ExpectStatus: http.StatusForbidden, - ExpectBody: httprequest.RemoteError{ - Code: "forbidden", - Message: "no pprof access configured", - }, - }) -} - -var debugTracePaths = []string{ - "/debug/events", - "/debug/requests", -} - -func (s *handlerSuite) TestServeTraceEvents(c *gc.C) { - httpHandler := newHTTPHandler(&debugstatus.Handler{ - CheckTraceAllowed: func(req *http.Request) (bool, error) { - if req.Header.Get("Authorization") == "" { - return false, errUnauthorized - } - return false, nil - }, - }) - authHeader := make(http.Header) - authHeader.Set("Authorization", "let me in") - for i, path := range debugTracePaths { - c.Logf("%d. %s", i, path) - httptesting.AssertJSONCall(c, httptesting.JSONCallParams{ - Handler: httpHandler, - URL: path, - ExpectStatus: http.StatusUnauthorized, - ExpectBody: httprequest.RemoteError{ - Code: "unauthorized", - Message: "you shall not pass!", - }, - }) - rr := httptesting.DoRequest(c, httptesting.DoRequestParams{ - Handler: httpHandler, - URL: path, - Header: authHeader, - }) - c.Assert(rr.Code, gc.Equals, http.StatusOK) - } -} - -func (s *handlerSuite) TestDebugEventsForbiddenWhenNotConfigured(c *gc.C) { - httpHandler := newHTTPHandler(&debugstatus.Handler{}) - httptesting.AssertJSONCall(c, httptesting.JSONCallParams{ - Handler: httpHandler, - URL: "/debug/events", - ExpectStatus: http.StatusForbidden, - ExpectBody: httprequest.RemoteError{ - Code: "forbidden", - Message: "no trace access configured", - }, - }) -} diff --git a/debugstatus/package_test.go b/debugstatus/package_test.go deleted file mode 100644 index 897570a5..00000000 --- a/debugstatus/package_test.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2014 Canonical Ltd. -// Licensed under the LGPLv3, see LICENCE file for details. - -package debugstatus_test - -import ( - "testing" - - gc "gopkg.in/check.v1" -) - -func TestPackage(t *testing.T) { - gc.TestingT(t) -} diff --git a/debugstatus/status.go b/debugstatus/status.go deleted file mode 100644 index 71b52504..00000000 --- a/debugstatus/status.go +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2014 Canonical Ltd. -// Licensed under the LGPLv3, see LICENCE file for details. - -// Package debugstatus provides facilities for inspecting information -// about a running HTTP service. -package debugstatus - -import ( - "fmt" - "sync" - "time" - - "golang.org/x/net/context" - - "github.com/juju/mgo/v2" -) - -// Check collects the status check results from the given checkers. -func Check(ctx context.Context, checkers ...CheckerFunc) map[string]CheckResult { - var mu sync.Mutex - results := make(map[string]CheckResult, len(checkers)) - - var wg sync.WaitGroup - for _, c := range checkers { - c := c - wg.Add(1) - go func() { - defer wg.Done() - t0 := time.Now() - key, result := c(ctx) - result.Duration = time.Since(t0) - mu.Lock() - results[key] = result - mu.Unlock() - }() - } - wg.Wait() - return results -} - -// CheckResult holds the result of a single status check. -type CheckResult struct { - // Name is the human readable name for the check. - Name string - - // Value is the check result. - Value string - - // Passed reports whether the check passed. - Passed bool - - // Duration holds the duration that the - // status check took to run. - Duration time.Duration -} - -// CheckerFunc represents a function returning the check machine friendly key -// and the result. -type CheckerFunc func(ctx context.Context) (key string, result CheckResult) - -// StartTime holds the time that the code started running. -var StartTime = time.Now().UTC() - -// ServerStartTime reports the time when the application was started. -func ServerStartTime(context.Context) (key string, result CheckResult) { - return "server_started", CheckResult{ - Name: "Server started", - Value: StartTime.String(), - Passed: true, - } -} - -// Connection returns a status checker reporting whether the given Pinger is -// connected. -func Connection(p Pinger) CheckerFunc { - return func(context.Context) (key string, result CheckResult) { - result.Name = "MongoDB is connected" - if err := p.Ping(); err != nil { - result.Value = "Ping error: " + err.Error() - return "mongo_connected", result - } - result.Value = "Connected" - result.Passed = true - return "mongo_connected", result - } -} - -// Pinger is an interface that wraps the Ping method. -// It is implemented by mgo.Session. -type Pinger interface { - Ping() error -} - -var _ Pinger = (*mgo.Session)(nil) - -// MongoCollections returns a status checker checking that all the -// expected Mongo collections are present in the database. -func MongoCollections(c Collector) CheckerFunc { - return func(context.Context) (key string, result CheckResult) { - key = "mongo_collections" - result.Name = "MongoDB collections" - names, err := c.CollectionNames() - if err != nil { - result.Value = "Cannot get collections: " + err.Error() - return key, result - } - var missing []string - for _, coll := range c.Collections() { - found := false - for _, name := range names { - if name == coll.Name { - found = true - break - } - } - if !found { - missing = append(missing, coll.Name) - } - } - if len(missing) == 0 { - result.Value = "All required collections exist" - result.Passed = true - return key, result - } - result.Value = fmt.Sprintf("Missing collections: %s", missing) - return key, result - } -} - -// Collector is an interface that groups the methods used to check that -// a Mongo database has the expected collections. -// It is usually implemented by types extending mgo.Database to add the -// Collections() method. -type Collector interface { - // Collections returns the Mongo collections that we expect to exist in - // the Mongo database. - Collections() []*mgo.Collection - - // CollectionNames returns the names of the collections actually present in - // the Mongo database. - CollectionNames() ([]string, error) -} - -// Rename changes the key and/or result name returned by the given check. -// It is possible to pass an empty string to avoid changing one of the values. -// This means that if both key are name are empty, this closure is a no-op. -func Rename(newKey, newName string, check CheckerFunc) CheckerFunc { - return func(ctx context.Context) (key string, result CheckResult) { - key, result = check(ctx) - if newKey == "" { - newKey = key - } - if newName != "" { - result.Name = newName - } - return newKey, result - } -} diff --git a/debugstatus/status_test.go b/debugstatus/status_test.go deleted file mode 100644 index d5a498fa..00000000 --- a/debugstatus/status_test.go +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2014 Canonical Ltd. -// Licensed under the LGPLv3, see LICENCE file for details. - -package debugstatus_test - -import ( - "errors" - "time" - - "github.com/juju/mgo/v2" - jujutesting "github.com/juju/testing" - jc "github.com/juju/testing/checkers" - "golang.org/x/net/context" - gc "gopkg.in/check.v1" - - "github.com/juju/utils/v3/debugstatus" -) - -type statusSuite struct { - jujutesting.IsolationSuite -} - -var _ = gc.Suite(&statusSuite{}) - -func makeCheckerFunc(key, name, value string, passed bool) debugstatus.CheckerFunc { - return func(context.Context) (string, debugstatus.CheckResult) { - time.Sleep(time.Microsecond) - return key, debugstatus.CheckResult{ - Name: name, - Value: value, - Passed: passed, - } - } -} - -func (s *statusSuite) TestCheck(c *gc.C) { - results := debugstatus.Check( - context.Background(), - makeCheckerFunc("check1", "check1 name", "value1", true), - makeCheckerFunc("check2", "check2 name", "value2", false), - makeCheckerFunc("check3", "check3 name", "value3", true), - ) - for key, r := range results { - if r.Duration < time.Microsecond { - c.Errorf("got %v want >1µs", r.Duration) - } - r.Duration = 0 - results[key] = r - } - - c.Assert(results, jc.DeepEquals, map[string]debugstatus.CheckResult{ - "check1": { - Name: "check1 name", - Value: "value1", - Passed: true, - }, - "check2": { - Name: "check2 name", - Value: "value2", - Passed: false, - }, - "check3": { - Name: "check3 name", - Value: "value3", - Passed: true, - }, - }) -} - -func (s *statusSuite) TestServerStartTime(c *gc.C) { - startTime := time.Now() - s.PatchValue(&debugstatus.StartTime, startTime) - key, result := debugstatus.ServerStartTime(context.Background()) - c.Assert(key, gc.Equals, "server_started") - c.Assert(result, jc.DeepEquals, debugstatus.CheckResult{ - Name: "Server started", - Value: startTime.String(), - Passed: true, - }) -} - -func (s *statusSuite) TestConnection(c *gc.C) { - // Ensure a connection established is properly reported. - check := debugstatus.Connection(pinger{nil}) - key, result := check(context.Background()) - c.Assert(key, gc.Equals, "mongo_connected") - c.Assert(result, jc.DeepEquals, debugstatus.CheckResult{ - Name: "MongoDB is connected", - Value: "Connected", - Passed: true, - }) - - // An error is reported if ping fails. - check = debugstatus.Connection(pinger{errors.New("bad wolf")}) - key, result = check(context.Background()) - c.Assert(key, gc.Equals, "mongo_connected") - c.Assert(result, jc.DeepEquals, debugstatus.CheckResult{ - Name: "MongoDB is connected", - Value: "Ping error: bad wolf", - Passed: false, - }) -} - -// pinger implements a debugstatus.Pinger used for tests. -type pinger struct { - err error -} - -func (p pinger) Ping() error { - return p.err -} - -var mongoCollectionsTests = []struct { - about string - collector collector - expectValue string - expectPassed bool -}{{ - about: "all collection exist", - collector: collector{ - expected: []string{"coll1", "coll2"}, - obtained: []string{"coll1", "coll2"}, - }, - expectValue: "All required collections exist", - expectPassed: true, -}, { - about: "no collections", - expectValue: "All required collections exist", - expectPassed: true, -}, { - about: "missing collections", - collector: collector{ - expected: []string{"coll1", "coll2", "coll3"}, - obtained: []string{"coll2"}, - }, - expectValue: "Missing collections: [coll1 coll3]", -}, { - about: "error retrieving collections", - collector: collector{ - err: errors.New("bad wolf"), - }, - expectValue: "Cannot get collections: bad wolf", -}} - -func (s *statusSuite) TestMongoCollections(c *gc.C) { - for i, test := range mongoCollectionsTests { - c.Logf("test %d: %s", i, test.about) - - // Ensure a connection established is properly reported. - check := debugstatus.MongoCollections(test.collector) - key, result := check(context.Background()) - c.Assert(key, gc.Equals, "mongo_collections") - c.Assert(result, jc.DeepEquals, debugstatus.CheckResult{ - Name: "MongoDB collections", - Value: test.expectValue, - Passed: test.expectPassed, - }) - } -} - -// collector implements a debugstatus.Collector used for tests. -type collector struct { - expected []string - obtained []string - err error -} - -func (c collector) CollectionNames() ([]string, error) { - return c.obtained, c.err -} - -func (c collector) Collections() []*mgo.Collection { - collections := make([]*mgo.Collection, len(c.expected)) - for i, name := range c.expected { - collections[i] = &mgo.Collection{Name: name} - } - return collections -} - -var renameTests = []struct { - about string - key string - name string -}{{ - about: "rename key", - key: "new-key", -}, { - about: "rename name", - name: "new name", -}, { - about: "rename both", - key: "another-key", - name: "another name", -}, { - about: "do not rename", -}} - -func (s *statusSuite) TestRename(c *gc.C) { - check := makeCheckerFunc("old-key", "old name", "value", true) - for i, test := range renameTests { - c.Logf("test %d: %s", i, test.about) - - // Rename and run the check. - key, result := debugstatus.Rename(test.key, test.name, check)(context.Background()) - - // Ensure the results are successfully renamed. - expectKey := test.key - if expectKey == "" { - expectKey = "old-key" - } - expectName := test.name - if expectName == "" { - expectName = "old name" - } - c.Assert(key, gc.Equals, expectKey) - c.Assert(result, jc.DeepEquals, debugstatus.CheckResult{ - Name: expectName, - Value: "value", - Passed: true, - }) - } -} diff --git a/go.mod b/go.mod index f91da0a8..c9047f63 100644 --- a/go.mod +++ b/go.mod @@ -3,23 +3,19 @@ module github.com/juju/utils/v3 go 1.17 require ( - github.com/juju/clock v0.0.0-20220202072423-1b0f830854c4 + github.com/juju/clock v0.0.0-20220203021603-d9deb868a28a github.com/juju/cmd/v3 v3.0.0-20220202061353-b1cc80b193b0 - github.com/juju/collections v0.0.0-20200605021417-0d0ec82b7271 - github.com/juju/errors v0.0.0-20210818161939-5560c4c073ff - github.com/juju/httpprof v0.0.0-20141217160036-14bf14c30767 + github.com/juju/collections v0.0.0-20220203020748-febd7cad8a7a + github.com/juju/errors v0.0.0-20220203013757-bd733f3c86b9 github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4 - github.com/juju/mgo/v2 v2.0.0-20210302023703-70d5d206e208 - github.com/juju/mutex/v2 v2.0.0-20220128011612-57176ebdcfa3 - github.com/juju/testing v0.0.0-20220202055744-1ad0816210a6 - github.com/julienschmidt/httprouter v1.1.1-0.20151013225520-77a895ad01eb + github.com/juju/mutex/v2 v2.0.0-20220203023141-11eeddb42c6c + github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494 github.com/masterzen/winrm v0.0.0-20211231115050-232efb40349e golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 golang.org/x/net v0.0.0-20211216030914-fe4d6282115f golang.org/x/text v0.3.7 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/errgo.v1 v1.0.0-20161222125816-442357a80af5 - gopkg.in/httprequest.v1 v1.1.1 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 gopkg.in/yaml.v2 v2.4.0 ) @@ -37,8 +33,9 @@ require ( github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/juju/ansiterm v0.0.0-20210706145210-9283cdf370b5 // indirect github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d // indirect + github.com/juju/mgo/v2 v2.0.0-20210302023703-70d5d206e208 // indirect github.com/juju/retry v0.0.0-20180821225755-9058e192b216 // indirect - github.com/juju/version v0.0.0-20191219164919-81c1be00b9a6 // indirect + github.com/juju/version/v2 v2.0.0-20211007103408-2e8da085dc23 // indirect github.com/kr/pretty v0.2.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/lunixbochs/vtclean v1.0.0 // indirect @@ -47,6 +44,4 @@ require ( github.com/mattn/go-isatty v0.0.13 // indirect golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect - gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect - launchpad.net/gocheck v0.0.0-20140225173054-000000000087 // indirect ) diff --git a/go.sum b/go.sum index a4fc5d8b..bae962c5 100644 --- a/go.sum +++ b/go.sum @@ -34,21 +34,23 @@ github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx github.com/juju/ansiterm v0.0.0-20210706145210-9283cdf370b5 h1:Q5klzs6BL5FkassBX65t+KkG0XjYcjxEm+GNcQAsuaw= github.com/juju/ansiterm v0.0.0-20210706145210-9283cdf370b5/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA= -github.com/juju/clock v0.0.0-20220202072423-1b0f830854c4 h1:9vNh7Dsk+O3rJD7pcem7tRSU1t+6aJDcjt0+bljWEKs= github.com/juju/clock v0.0.0-20220202072423-1b0f830854c4/go.mod h1:zDZCPSgCJQINeZtQwHx2/cFk4seaBC8Yiqe8V82xiP0= +github.com/juju/clock v0.0.0-20220203021603-d9deb868a28a h1:Az/6CM/P5guGHNy7r6TkOCctv3lDmN3W1uhku7QMupk= +github.com/juju/clock v0.0.0-20220203021603-d9deb868a28a/go.mod h1:GZ/FY8Cqw3KHG6DwRVPUKbSPTAwyrU28xFi5cqZnLsc= github.com/juju/cmd v0.0.0-20171107070456-e74f39857ca0 h1:kMNSBOBQHgDocCDaItn5Gw/nR6P1RwqB/QyLtINvAMY= github.com/juju/cmd v0.0.0-20171107070456-e74f39857ca0/go.mod h1:yWJQHl73rdSX4DHVKGqkAip+huBslxRwS8m9CrOLq18= github.com/juju/cmd/v3 v3.0.0-20220202061353-b1cc80b193b0 h1:DZ0mfFDpt4SXi+krwruQw3y9wbUn3tM5Jyp2bS9iRDg= github.com/juju/cmd/v3 v3.0.0-20220202061353-b1cc80b193b0/go.mod h1:EoGJiEG+vbMwO9l+Es0SDTlaQPjH6nLcnnc4NfZB3cY= -github.com/juju/collections v0.0.0-20200605021417-0d0ec82b7271 h1:4R626WTwa7pRYQFiIRLVPepMhm05eZMEx+wIurRnMLc= github.com/juju/collections v0.0.0-20200605021417-0d0ec82b7271/go.mod h1:5XgO71dV1JClcOJE+4dzdn4HrI5LiyKd7PlVG6eZYhY= +github.com/juju/collections v0.0.0-20220203020748-febd7cad8a7a h1:d7eZO8OS/ZXxdP0uq3E8CdoA1qNFaecAv90UxrxaY2k= +github.com/juju/collections v0.0.0-20220203020748-febd7cad8a7a/go.mod h1:JWeZdyttIEbkR51z2S13+J+aCuHVe0F6meRy+P0YGDo= github.com/juju/errors v0.0.0-20150916125642-1b5e39b83d18/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/errors v0.0.0-20200330140219-3fe23663418f/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= -github.com/juju/errors v0.0.0-20210818161939-5560c4c073ff h1:WLHwK6yMswDvGUNrkxp4GYnrbQS8WULu1D3qteVdUIg= github.com/juju/errors v0.0.0-20210818161939-5560c4c073ff/go.mod h1:i1eL7XREII6aHpQ2gApI/v6FkVUDEBremNkcBCKYAcY= +github.com/juju/errors v0.0.0-20220203013757-bd733f3c86b9 h1:EJHbsNpQyupmMeWTq7inn+5L/WZ7JfzCVPJ+DP9McCQ= +github.com/juju/errors v0.0.0-20220203013757-bd733f3c86b9/go.mod h1:TRm7EVGA3mQOqSVcBySRY7a9Y1/gyVhh/WTCnc5sD4U= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d h1:c93kUJDtVAXFEhsCh5jSxyOJmFHuzcihnslQiX8Urwo= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= -github.com/juju/httpprof v0.0.0-20141217160036-14bf14c30767 h1:COsaGcfAONDdIDnGS8yFdxOyReP7zKQEr7jFzCHKDkM= github.com/juju/httpprof v0.0.0-20141217160036-14bf14c30767/go.mod h1:+MaLYz4PumRkkyHYeXJ2G5g5cIW0sli2bOfpmbaMV/g= github.com/juju/loggo v0.0.0-20170605014607-8232ab8918d9/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= github.com/juju/loggo v0.0.0-20200526014432-9ce3a2e09b5e/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= @@ -58,8 +60,9 @@ github.com/juju/mgo/v2 v2.0.0-20210302023703-70d5d206e208 h1:/WiCm+Vpj87e4QWuWwP github.com/juju/mgo/v2 v2.0.0-20210302023703-70d5d206e208/go.mod h1:0OChplkvPTZ174D2FYZXg4IB9hbEwyHkD+zT+/eK+Fg= github.com/juju/mutex v0.0.0-20171110020013-1fe2a4bf0a3a h1:fFpWkIPzpzxd3CC+qfU5adsawpG371Ie4TmRkhxwyNU= github.com/juju/mutex v0.0.0-20171110020013-1fe2a4bf0a3a/go.mod h1:Y3oOzHH8CQ0Ppt0oCKJ2JFO81/EsWenH5AEqigLH+yY= -github.com/juju/mutex/v2 v2.0.0-20220128011612-57176ebdcfa3 h1:26ZmuBEXYSExkU2lLdMYBpxtK3/xXOgOfeCUNpNWcA8= github.com/juju/mutex/v2 v2.0.0-20220128011612-57176ebdcfa3/go.mod h1:TTCG9BJD9rCC4DZFz3jA0QvCqFDHw8Eqz0jstwY7RTQ= +github.com/juju/mutex/v2 v2.0.0-20220203023141-11eeddb42c6c h1:+wSxXZCFPw3Bs5ij3ZMV0njIKGVwl4+Ftb5vb0ij+7Y= +github.com/juju/mutex/v2 v2.0.0-20220203023141-11eeddb42c6c/go.mod h1:jwCfBs/smYDaeZLqeaCi8CB8M+tOes4yf827HoOEoqk= github.com/juju/retry v0.0.0-20151029024821-62c620325291/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4= github.com/juju/retry v0.0.0-20180821225755-9058e192b216 h1:/eQL7EJQKFHByJe3DeE8Z36yqManj9UY5zppDoQi4FU= github.com/juju/retry v0.0.0-20180821225755-9058e192b216/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4= @@ -68,18 +71,21 @@ github.com/juju/testing v0.0.0-20180517134105-72703b1e95eb/go.mod h1:63prj8cnj0t github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/juju/testing v0.0.0-20210302031854-2c7ee8570c07/go.mod h1:7lxZW0B50+xdGFkvhAb8bwAGt6IU87JB1H9w4t8MNVM= github.com/juju/testing v0.0.0-20210324180055-18c50b0c2098/go.mod h1:7lxZW0B50+xdGFkvhAb8bwAGt6IU87JB1H9w4t8MNVM= -github.com/juju/testing v0.0.0-20220202055744-1ad0816210a6 h1:ulhR1Mns3DtuGP+dmHV92CBqgsNT7FsE3sFOePwHFVI= github.com/juju/testing v0.0.0-20220202055744-1ad0816210a6/go.mod h1:QgWc2UdIPJ8t3rnvv95tFNOsQDfpXYEZDbP281o3b2c= +github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494 h1:XEDzpuZb8Ma7vLja3+5hzUqVTvAqm5Y+ygvnDs5iTMM= +github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494/go.mod h1:rUquetT0ALL48LHZhyRGvjjBH8xZaZ8dFClulKK5wK4= github.com/juju/utils v0.0.0-20180424094159-2000ea4ff043/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk= github.com/juju/utils v0.0.0-20200116185830-d40c2fe10647 h1:wQpkHVbIIpz1PCcLYku9KFWsJ7aEMQXHBBmLy3tRBTk= github.com/juju/utils v0.0.0-20200116185830-d40c2fe10647/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk= github.com/juju/utils/v2 v2.0.0-20200923005554-4646bfea2ef1/go.mod h1:fdlDtQlzundleLLz/ggoYinEt/LmnrpNKcNTABQATNI= github.com/juju/utils/v3 v3.0.0-20220130232349-cd7ecef0e94a/go.mod h1:LzwbbEN7buYjySp4nqnti6c6olSqRXUk6RkbSUUP1n8= +github.com/juju/utils/v3 v3.0.0-20220202114721-338bb0530e89/go.mod h1:wf5w+8jyTh2IYnSX0sHnMJo4ZPwwuiBWn+xN3DkQg4k= github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= github.com/juju/version v0.0.0-20191219164919-81c1be00b9a6 h1:nrqc9b4YKpKV4lPI3GPPFbo5FUuxkWxgZE2Z8O4lgaw= github.com/juju/version v0.0.0-20191219164919-81c1be00b9a6/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= -github.com/julienschmidt/httprouter v1.1.1-0.20151013225520-77a895ad01eb h1:Ano5YqfI4cpXMCS5aOGv92G7Mvb8nMj+t4BGMXvOvgQ= +github.com/juju/version/v2 v2.0.0-20211007103408-2e8da085dc23 h1:wtEPbidt1VyHlb8RSztU6ySQj29FLsOQiI9XiJhXDM4= +github.com/juju/version/v2 v2.0.0-20211007103408-2e8da085dc23/go.mod h1:Ljlbryh9sYaUSGXucslAEDf0A2XUSGvDbHJgW8ps6nc= github.com/julienschmidt/httprouter v1.1.1-0.20151013225520-77a895ad01eb/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= @@ -163,10 +169,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v1 v1.0.0-20161222125816-442357a80af5 h1:C61FEf19ZFrkIbSSCWRHvzrtgrcRh/kiH4HZ5VIIqe8= gopkg.in/errgo.v1 v1.0.0-20161222125816-442357a80af5/go.mod h1:u0ALmqvLRxLI95fkdCEWrE6mhWYZW1aMOJHp5YXLHTg= -gopkg.in/httprequest.v1 v1.1.1 h1:dgGhb+TJN/mB/njcl4w/qljft78Pe4GwpTz6FZD3aqQ= gopkg.in/httprequest.v1 v1.1.1/go.mod h1:/CkavNL+g3qLOrpFHVrEx4NKepeqR4XTZWNj4sGGjz0= gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/mgokv/export_test.go b/mgokv/export_test.go deleted file mode 100644 index 1baef69d..00000000 --- a/mgokv/export_test.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2017 Canonical Ltd. -// Licensed under the LGPLv3, see LICENCE file for details. - -package mgokv - -var ( - PutInitialAtTime = (*Session).putInitialAtTime - PutAtTime = (*Session).putAtTime - GetAtTime = (*Session).getAtTime -) diff --git a/mgokv/package_test.go b/mgokv/package_test.go deleted file mode 100644 index 126ad532..00000000 --- a/mgokv/package_test.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2017 Canonical Ltd. -// Licensed under the LGPLv3, see LICENCE file for details. - -package mgokv_test - -import ( - "testing" - - jujutesting "github.com/juju/testing" -) - -func TestPackage(t *testing.T) { - jujutesting.MgoTestPackage(t, nil) -} diff --git a/mgokv/persist.go b/mgokv/persist.go deleted file mode 100644 index dcd84478..00000000 --- a/mgokv/persist.go +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright 2017 Canonical Ltd. -// Licensed under the LGPLv3, see LICENCE file for details. - -// Package mgokv defines cached MongoDB-backed global persistent storage for -// key-value pairs. -// -// It is designed to be used when there is a small set of attributes that change infrequently. -// It shouldn't be used when there's an unbounded set of keys, as key -// entries are not deleted. -package mgokv - -import ( - "sync" - "time" - - "github.com/juju/mgo/v2" - "github.com/juju/mgo/v2/bson" - "gopkg.in/errgo.v1" -) - -// ErrNotFound is returned as the cause of the error -// when an entry is not found. -var ErrNotFound = errgo.New("persistent data entry not found") - -type entry struct { - // value holds the BSON-marshaled value. It will be empty - // if the entry was not found when fetched. - value []byte - // expire holds when the value will expire - // from the cache. This will be zero when the - // value does not exist. - expire time.Time -} - -// Store represents a cached set of key-value pairs. -type Store struct { - cacheLifetime time.Duration - mu sync.RWMutex - // entries holds all the cached entries. - entries map[string]entry - coll *mgo.Collection -} - -// Refresh forgets all cached items. -func (s *Store) Refresh() { - s.mu.Lock() - s.entries = make(map[string]entry) - s.mu.Unlock() -} - -// NewStore returns a Store that will cache items for at most the given -// time in the given collection. The session in the collection will not -// be used - the session passed to Store.Session will be used instead. -func NewStore(cacheLifetime time.Duration, c *mgo.Collection) *Store { - return &Store{ - entries: make(map[string]entry), - cacheLifetime: cacheLifetime, - coll: c, - } -} - -// Session associates a Store instance with a mongo session. -type Session struct { - *Store - coll *mgo.Collection -} - -// entryDoc holds the document that's stored in MongoDB. -type entryDoc struct { - Key string `bson:"_id"` - // Value holds the value. We store it as a raw value - // so that it looks nice when looking at the collection directly. - Value bson.Raw `bson:"value"` -} - -// Session returns a store session that uses the given -// session for storage. Each store entry is stored -// in a document in the collection. -func (s *Store) Session(session *mgo.Session) *Session { - return &Session{ - Store: s, - coll: s.coll.With(session), - } -} - -// Put stores the given value for the given key. The value must be a struct type that is -// marshalable as BSON (see http://gopkg.in/mgo.v2/bson). -func (s *Session) Put(key string, val interface{}) error { - return s.putAtTime(key, val, time.Now()) -} - -// putAtTime is the internal version of Put - it takes the current time -// as an argument for testing. -func (s *Session) putAtTime(key string, val interface{}, now time.Time) error { - data, err := bson.Marshal(val) - if err != nil { - return errgo.Mask(err) - } - s.mu.Lock() - defer s.mu.Unlock() - _, err = s.coll.UpsertId(key, bson.D{{ - "$set", bson.D{{"value", bson.Raw{ - Kind: 3, - Data: data, - }}}, - }}) - if err != nil { - return errgo.Notef(err, "cannot put %q", key) - } - s.entries[key] = entry{ - expire: now.Add(s.cacheLifetime), - value: data, - } - return nil -} - -// PutInitial puts an initial value for the given key. It does -// nothing if there is already a value stored for the key. -// It reports whether the value was actually set. -func (s *Session) PutInitial(key string, val interface{}) (bool, error) { - return s.putInitialAtTime(key, val, time.Now()) -} - -// Update updates the value using the MongoDB update operation -// specified in update. The value is stored in the "value" field -// in the document. -// -// For example, if a value of type struct { N int } is associated -// with a key, then: -// -// s.Update(key, bson.M{"$inc": bson.M{"value.n": 1}}) -// -// will atomically increment the N value. -// -// If there is no value associated with the key, Update -// returns ErrNotFound. -func (s *Session) Update(key string, update interface{}) error { - s.mu.Lock() - defer s.mu.Unlock() - if err := s.coll.UpdateId(key, update); err != nil { - if err == mgo.ErrNotFound { - return ErrNotFound - } - return errgo.Mask(err) - } - // We can't easily find the new value so just delete the - // item from the cache so it will be fetched next time. - delete(s.entries, key) - return nil -} - -// putInitialAtTime is the internal version of PutInitial - it takes the current time -// as an argument for testing. -func (s *Session) putInitialAtTime(key string, val interface{}, now time.Time) (bool, error) { - data, err := bson.Marshal(val) - if err != nil { - return false, errgo.Mask(err) - } - s.mu.Lock() - defer s.mu.Unlock() - err = s.coll.Insert(&entryDoc{ - Key: key, - Value: bson.Raw{ - Kind: 3, - Data: data, - }, - }) - if mgo.IsDup(err) { - return false, nil - } - if err != nil { - return false, errgo.Mask(err) - } - s.entries[key] = entry{ - expire: now.Add(s.cacheLifetime), - value: data, - } - return true, nil -} - -// Get gets the value associated with the given key into the -// value pointed to by v, which should be a pointer to -// the same struct type used to put the value originally. -// -// If the value is not found, it returns ErrNotFound. -func (s *Session) Get(key string, v interface{}) error { - return s.getAtTime(key, v, time.Now()) -} - -// getAtTime is the internal version of Get - it takes the current time -// as an argument for testing. -func (s *Session) getAtTime(key string, v interface{}, now time.Time) error { - e, err := s.getEntryAtTime(key, now) - if err != nil { - return errgo.Mask(err) - } - if e.value == nil { - return ErrNotFound - } - if err := bson.Unmarshal(e.value, v); err != nil { - return errgo.Notef(err, "cannot unmarshal data for key %q into %T", key, v) - } - return nil -} - -func (s *Session) getEntryAtTime(key string, now time.Time) (entry, error) { - s.mu.RLock() - e, ok := s.entries[key] - s.mu.RUnlock() - if ok && now.Before(e.expire) { - return e, nil - } - s.mu.Lock() - defer s.mu.Unlock() - e, ok = s.entries[key] - if ok && now.Before(e.expire) { - return e, nil - } - var doc entryDoc - if err := s.coll.FindId(key).One(&doc); err != nil { - if err != mgo.ErrNotFound { - return entry{}, errgo.Notef(err, "cannot retrieve data for key %q", key) - } - } - e = entry{ - value: doc.Value.Data, - expire: now.Add(s.cacheLifetime), - } - s.entries[key] = e - return e, nil -} diff --git a/mgokv/persist_test.go b/mgokv/persist_test.go deleted file mode 100644 index 19e6e872..00000000 --- a/mgokv/persist_test.go +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright 2017 Canonical Ltd. -// Licensed under the LGPLv3, see LICENCE file for details. - -package mgokv_test - -import ( - "sync" - "time" - - "github.com/juju/mgo/v2/bson" - "github.com/juju/testing" - gc "gopkg.in/check.v1" - "gopkg.in/errgo.v1" - - "github.com/juju/utils/v3/mgokv" -) - -type suite struct { - testing.MgoSuite -} - -var _ = gc.Suite(&suite{}) - -func (s *suite) TestPutInitial(c *gc.C) { - type val struct { - A, B int - } - store := mgokv.NewStore(10*time.Second, s.Session.DB("foo").C("x")).Session(s.Session) - ok, err := store.PutInitial("key", val{1, 2}) - c.Assert(err, gc.Equals, nil) - c.Assert(ok, gc.Equals, true) - - var v val - err = store.Get("key", &v) - c.Assert(err, gc.Equals, nil) - c.Assert(v, gc.Equals, val{1, 2}) - - // Check that it really is stored in the database by - // using a fresh store to access it. - store = mgokv.NewStore(time.Second, s.Session.DB("foo").C("x")).Session(s.Session) - v = val{} - err = store.Get("key", &v) - c.Assert(err, gc.Equals, nil) - c.Assert(v, gc.Equals, val{1, 2}) - - // The second time PutInitial is called, it should do nothing. - ok, err = store.PutInitial("key", val{99, 100}) - c.Assert(err, gc.Equals, nil) - c.Assert(ok, gc.Equals, false) - - // The value should not have changed in the cache... - v = val{} - err = store.Get("key", &v) - c.Assert(err, gc.Equals, nil) - c.Assert(v, gc.Equals, val{1, 2}) - - // ... or in the database itself. - store = mgokv.NewStore(time.Second, s.Session.DB("foo").C("x")).Session(s.Session) - v = val{} - err = store.Get("key", &v) - c.Assert(err, gc.Equals, nil) - c.Assert(v, gc.Equals, val{1, 2}) -} - -func (s *suite) TestPutGet(c *gc.C) { - type val struct { - A, B int - } - t0 := time.Now() - store := mgokv.NewStore(time.Second, s.Session.DB("foo").C("x")).Session(s.Session) - err := mgokv.PutAtTime(store, "key", val{1, 2}, t0) - c.Assert(err, gc.Equals, nil) - - var v val - err = mgokv.GetAtTime(store, "key", &v, t0.Add(time.Millisecond)) - c.Assert(err, gc.Equals, nil) - c.Assert(v, gc.Equals, val{1, 2}) - - // If we put a value into the database in another store, the value - // in the original store will persist until the cache expires. - store1 := mgokv.NewStore(time.Second, s.Session.DB("foo").C("x")).Session(s.Session) - err = mgokv.PutAtTime(store1, "key", val{88, 99}, t0) - c.Assert(err, gc.Equals, nil) - - // Just before the deadline we still see the original value. - err = mgokv.GetAtTime(store, "key", &v, t0.Add(time.Second-1)) - c.Assert(err, gc.Equals, nil) - c.Assert(v, gc.Equals, val{1, 2}) - - // After the deadline, we see the new value. - err = mgokv.GetAtTime(store, "key", &v, t0.Add(time.Second)) - c.Assert(err, gc.Equals, nil) - c.Assert(v, gc.Equals, val{88, 99}) -} - -func (s *suite) TestNotFound(c *gc.C) { - type val struct { - A, B int - } - t0 := time.Now() - store := mgokv.NewStore(time.Second, s.Session.DB("foo").C("x")).Session(s.Session) - var v val - err := mgokv.GetAtTime(store, "key", &v, t0) - c.Assert(errgo.Cause(err), gc.Equals, mgokv.ErrNotFound) - c.Assert(v, gc.Equals, val{}) - - // Use another store to store a value. The original store should - // not see the new value until the not-found entry has expired. - store1 := mgokv.NewStore(time.Second, s.Session.DB("foo").C("x")).Session(s.Session) - err = mgokv.PutAtTime(store1, "key", val{1, 2}, t0) - c.Assert(err, gc.Equals, nil) - - // Just before the deadline we still see the not-found error. - err = mgokv.GetAtTime(store, "key", &v, t0.Add(time.Second-1)) - c.Assert(errgo.Cause(err), gc.Equals, mgokv.ErrNotFound) - c.Assert(v, gc.Equals, val{}) - - err = mgokv.GetAtTime(store, "key", &v, t0.Add(time.Second)) - c.Assert(err, gc.Equals, nil) - c.Assert(v, gc.Equals, val{1, 2}) -} - -func (s *suite) TestMultipleKeys(c *gc.C) { - type val struct { - A, B int - } - store := mgokv.NewStore(time.Second, s.Session.DB("foo").C("x")).Session(s.Session) - - err := store.Put("key1", val{1, 2}) - c.Assert(err, gc.Equals, nil) - - err = store.Put("key2", val{3, 4}) - c.Assert(err, gc.Equals, nil) - - var v val - err = store.Get("key1", &v) - c.Assert(err, gc.Equals, nil) - c.Assert(v, gc.Equals, val{1, 2}) - - err = store.Get("key2", &v) - c.Assert(err, gc.Equals, nil) - c.Assert(v, gc.Equals, val{3, 4}) -} - -func (s *suite) TestConcurrentGet(c *gc.C) { - // This test is designed to run with the race detector enabled. - - type val struct { - A, B int - } - // Put a value into the store. - store := mgokv.NewStore(time.Second, s.Session.DB("foo").C("x")).Session(s.Session) - err := store.Put("key", val{1, 2}) - c.Check(err, gc.Equals, nil) - - // Use a new store so that we haven't already got a cache entry. - store = mgokv.NewStore(time.Second, s.Session.DB("foo").C("x")).Session(s.Session) - var wg sync.WaitGroup - for i := 0; i < 5; i++ { - wg.Add(1) - go func() { - defer wg.Done() - var v val - err := store.Get("key", &v) - c.Check(err, gc.Equals, nil) - c.Check(v, gc.Equals, val{1, 2}) - }() - } - wg.Wait() -} - -func (s *suite) TestRefresh(c *gc.C) { - type val struct { - A, B int - } - // Put a value into the store. - store := mgokv.NewStore(time.Second, s.Session.DB("foo").C("x")).Session(s.Session) - t0 := time.Now() - err := mgokv.PutAtTime(store, "key", val{1, 2}, t0) - c.Check(err, gc.Equals, nil) - - // Put a different value using a different store. - store1 := mgokv.NewStore(time.Second, s.Session.DB("foo").C("x")).Session(s.Session) - err = store1.Put("key", val{88, 99}) - c.Check(err, gc.Equals, nil) - - // Sanity check that the first store still retains the cached value. - var v val - err = mgokv.GetAtTime(store, "key", &v, t0.Add(time.Millisecond)) - c.Check(err, gc.Equals, nil) - c.Assert(v, gc.Equals, val{1, 2}) - - // Refresh the store and we should now see the new value. - store.Refresh() - - err = mgokv.GetAtTime(store, "key", &v, t0.Add(time.Millisecond)) - c.Check(err, gc.Equals, nil) - c.Assert(v, gc.Equals, val{88, 99}) -} - -func (s *suite) TestUpdate(c *gc.C) { - type val struct { - N int - } - store := mgokv.NewStore(time.Minute, s.Session.DB("foo").C("x")).Session(s.Session) - _, err := store.PutInitial("somekey", val{2}) - c.Assert(err, gc.Equals, nil) - err = store.Update("somekey", bson.M{"$inc": bson.M{"value.n": 1}}) - c.Assert(err, gc.Equals, nil) - - var v val - err = store.Get("somekey", &v) - c.Assert(err, gc.IsNil) - c.Assert(v, gc.Equals, val{N: 3}) -} - -func (s *suite) TestUpdateNotFound(c *gc.C) { - store := mgokv.NewStore(time.Minute, s.Session.DB("foo").C("x")).Session(s.Session) - err := store.Update("somekey", bson.M{"$inc": bson.M{"value.n": 1}}) - c.Assert(err, gc.Equals, mgokv.ErrNotFound) -}