github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/bakerystorage/storage_test.go (about) 1 // Copyright 2014-2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package bakerystorage 5 6 import ( 7 "context" 8 "encoding/json" 9 "time" // Only used for time types. 10 11 "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" 12 "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery/checkers" 13 "github.com/juju/mgo/v3" 14 mgotesting "github.com/juju/mgo/v3/testing" 15 gitjujutesting "github.com/juju/testing" 16 jc "github.com/juju/testing/checkers" 17 gc "gopkg.in/check.v1" 18 "gopkg.in/macaroon.v2" 19 20 "github.com/juju/juju/mongo" 21 "github.com/juju/juju/testing" 22 ) 23 24 type StorageSuite struct { 25 testing.BaseSuite 26 gitjujutesting.Stub 27 collection mockCollection 28 memStorage bakery.RootKeyStore 29 closeCollection func() 30 config Config 31 } 32 33 var _ = gc.Suite(&StorageSuite{}) 34 35 func (s *StorageSuite) SetUpTest(c *gc.C) { 36 s.BaseSuite.SetUpTest(c) 37 s.Stub.ResetCalls() 38 s.collection = mockCollection{ 39 Stub: &s.Stub, 40 one: func(q *mockQuery, result *interface{}) error { 41 location := q.id.(string) 42 if location != "oldkey" { 43 return mgo.ErrNotFound 44 } 45 *(*result).(*storageDoc) = storageDoc{ 46 Location: q.id.(string), 47 Item: "{\"RootKey\":\"ibbhlQv5+yf7UMNI77W4hxQeQjRdMxs0\"}", 48 } 49 return nil 50 }, 51 } 52 s.closeCollection = func() { 53 s.AddCall("Close") 54 s.PopNoErr() 55 } 56 s.memStorage = bakery.NewMemRootKeyStore() 57 s.config = Config{ 58 GetCollection: func() (mongo.Collection, func()) { 59 s.AddCall("GetCollection") 60 s.PopNoErr() 61 return &s.collection, s.closeCollection 62 }, 63 GetStorage: func(rootKeys *RootKeys, coll mongo.Collection, expireAfter time.Duration) bakery.RootKeyStore { 64 s.AddCall("GetStorage", coll, expireAfter) 65 s.PopNoErr() 66 return s.memStorage 67 }, 68 } 69 } 70 71 func (s *StorageSuite) TestValidateConfigGetCollection(c *gc.C) { 72 s.config.GetCollection = nil 73 _, err := New(s.config) 74 c.Assert(err, gc.ErrorMatches, "validating config: nil GetCollection not valid") 75 } 76 77 func (s *StorageSuite) TestValidateConfigGetStorage(c *gc.C) { 78 s.config.GetStorage = nil 79 _, err := New(s.config) 80 c.Assert(err, gc.ErrorMatches, "validating config: nil GetStorage not valid") 81 } 82 83 func (s *StorageSuite) TestExpireAfter(c *gc.C) { 84 store, err := New(s.config) 85 c.Assert(err, jc.ErrorIsNil) 86 87 store = store.ExpireAfter(24 * time.Hour) 88 c.Assert(ExpireAfter(store), gc.Equals, 24*time.Hour) 89 } 90 91 func (s *StorageSuite) TestGet(c *gc.C) { 92 store, err := New(s.config) 93 c.Assert(err, jc.ErrorIsNil) 94 95 ctx := context.Background() 96 rootKey, id, err := store.RootKey(ctx) 97 c.Assert(err, jc.ErrorIsNil) 98 99 item, err := store.Get(ctx, id) 100 c.Assert(err, jc.ErrorIsNil) 101 c.Assert(item, jc.DeepEquals, rootKey) 102 s.CheckCalls(c, []gitjujutesting.StubCall{ 103 {"GetCollection", nil}, 104 {"GetStorage", []interface{}{&s.collection, time.Duration(0)}}, 105 {"Close", nil}, 106 {"GetCollection", nil}, 107 {"GetStorage", []interface{}{&s.collection, time.Duration(0)}}, 108 {"Close", nil}, 109 }) 110 } 111 112 func (s *StorageSuite) TestGetNotFound(c *gc.C) { 113 store, err := New(s.config) 114 c.Assert(err, jc.ErrorIsNil) 115 c.Log("1.") 116 _, err = store.Get(context.Background(), []byte("foo")) 117 c.Log("2.") 118 c.Assert(err, gc.Equals, bakery.ErrNotFound) 119 } 120 121 func (s *StorageSuite) TestGetLegacyFallback(c *gc.C) { 122 store, err := New(s.config) 123 c.Assert(err, jc.ErrorIsNil) 124 125 var rk legacyRootKey 126 err = json.Unmarshal([]byte("{\"RootKey\":\"ibbhlQv5+yf7UMNI77W4hxQeQjRdMxs0\"}"), &rk) 127 c.Assert(err, jc.ErrorIsNil) 128 129 item, err := store.Get(context.Background(), []byte("oldkey")) 130 c.Assert(err, jc.ErrorIsNil) 131 c.Assert(item, jc.DeepEquals, rk.RootKey) 132 s.CheckCalls(c, []gitjujutesting.StubCall{ 133 {"GetCollection", nil}, 134 {"GetStorage", []interface{}{&s.collection, time.Duration(0)}}, 135 {"GetCollection", nil}, 136 {"FindId", []interface{}{"oldkey"}}, 137 {"One", []interface{}{&storageDoc{ 138 // Set by mock, not in input. Unimportant anyway. 139 Location: "oldkey", 140 Item: "{\"RootKey\":\"ibbhlQv5+yf7UMNI77W4hxQeQjRdMxs0\"}", 141 }}}, 142 {"Close", nil}, 143 {"Close", nil}, 144 }) 145 } 146 147 type mockCollection struct { 148 mongo.WriteCollection 149 *gitjujutesting.Stub 150 151 one func(q *mockQuery, result *interface{}) error 152 } 153 154 func (c *mockCollection) FindId(id interface{}) mongo.Query { 155 c.MethodCall(c, "FindId", id) 156 c.PopNoErr() 157 return &mockQuery{Stub: c.Stub, id: id, one: c.one} 158 } 159 160 func (c *mockCollection) Writeable() mongo.WriteCollection { 161 c.MethodCall(c, "Writeable") 162 c.PopNoErr() 163 return c 164 } 165 166 type mockQuery struct { 167 mongo.Query 168 *gitjujutesting.Stub 169 id interface{} 170 one func(q *mockQuery, result *interface{}) error 171 } 172 173 func (q *mockQuery) One(result interface{}) error { 174 q.MethodCall(q, "One", result) 175 176 err := q.one(q, &result) 177 if err != nil { 178 return err 179 } 180 return q.NextErr() 181 } 182 183 var _ = gc.Suite(&BakeryStorageSuite{}) 184 185 type BakeryStorageSuite struct { 186 mgotesting.MgoSuite 187 gitjujutesting.LoggingSuite 188 189 store ExpirableStorage 190 bakery *bakery.Bakery 191 db *mgo.Database 192 coll *mgo.Collection 193 } 194 195 func (s *BakeryStorageSuite) SetUpTest(c *gc.C) { 196 s.MgoSuite.SetUpTest(c) 197 s.LoggingSuite.SetUpTest(c) 198 s.db = s.Session.DB("bakerydb") 199 s.coll = s.db.C("bakedgoods") 200 s.ensureIndexes(c) 201 s.initService(c, false) 202 } 203 204 func (s *BakeryStorageSuite) TearDownTest(c *gc.C) { 205 s.LoggingSuite.TearDownTest(c) 206 s.MgoSuite.TearDownTest(c) 207 } 208 209 func (s *BakeryStorageSuite) SetUpSuite(c *gc.C) { 210 s.LoggingSuite.SetUpSuite(c) 211 s.MgoSuite.SetUpSuite(c) 212 } 213 214 func (s *BakeryStorageSuite) TearDownSuite(c *gc.C) { 215 s.MgoSuite.TearDownSuite(c) 216 s.LoggingSuite.TearDownSuite(c) 217 } 218 219 func (s *BakeryStorageSuite) initService(c *gc.C, enableExpiry bool) { 220 store, err := New(Config{ 221 GetCollection: func() (mongo.Collection, func()) { 222 return mongo.CollectionFromName(s.db, s.coll.Name) 223 }, 224 GetStorage: func(rootKeys *RootKeys, coll mongo.Collection, expireAfter time.Duration) (storage bakery.RootKeyStore) { 225 return rootKeys.NewStore(coll.Writeable().Underlying(), Policy{ 226 ExpiryDuration: expireAfter, 227 }) 228 }, 229 }) 230 c.Assert(err, jc.ErrorIsNil) 231 if enableExpiry { 232 store = store.ExpireAfter(10 * time.Second) 233 } 234 s.store = store 235 s.bakery = bakery.New(bakery.BakeryParams{ 236 RootKeyStore: s.store, 237 }) 238 } 239 240 func (s *BakeryStorageSuite) ensureIndexes(c *gc.C) { 241 for _, index := range MongoIndexes() { 242 err := s.coll.EnsureIndex(index) 243 c.Assert(err, jc.ErrorIsNil) 244 } 245 } 246 247 func (s *BakeryStorageSuite) TestCheckNewMacaroon(c *gc.C) { 248 cav := []checkers.Caveat{{Condition: "something"}} 249 mac, err := s.bakery.Oven.NewMacaroon(context.TODO(), bakery.LatestVersion, cav, bakery.NoOp) 250 c.Assert(err, jc.ErrorIsNil) 251 _, _, err = s.bakery.Oven.VerifyMacaroon(context.TODO(), macaroon.Slice{mac.M()}) 252 c.Assert(err, gc.ErrorMatches, "verification failed: macaroon not found in storage") 253 254 store := s.store.ExpireAfter(10 * time.Second) 255 b := bakery.New(bakery.BakeryParams{ 256 RootKeyStore: store, 257 }) 258 mac, err = b.Oven.NewMacaroon(context.TODO(), bakery.LatestVersion, cav, bakery.NoOp) 259 c.Assert(err, jc.ErrorIsNil) 260 op, conditions, err := s.bakery.Oven.VerifyMacaroon(context.TODO(), macaroon.Slice{mac.M()}) 261 c.Assert(err, jc.ErrorIsNil) 262 c.Assert(op, jc.DeepEquals, []bakery.Op{bakery.NoOp}) 263 c.Assert(conditions, jc.DeepEquals, []string{"something"}) 264 } 265 266 func (s *BakeryStorageSuite) TestExpiryTime(c *gc.C) { 267 // Reinitialise bakery service with storage that will expire 268 // items immediately. 269 s.initService(c, true) 270 271 mac, err := s.bakery.Oven.NewMacaroon(context.TODO(), bakery.LatestVersion, nil, bakery.NoOp) 272 c.Assert(err, jc.ErrorIsNil) 273 274 // The background thread that removes records runs every 60s. 275 // Give a little bit of leeway for loaded systems. 276 for i := 0; i < 90; i++ { 277 _, _, err = s.bakery.Oven.VerifyMacaroon(context.TODO(), macaroon.Slice{mac.M()}) 278 if err == nil { 279 time.Sleep(time.Second) 280 continue 281 } 282 c.Assert(err, gc.ErrorMatches, "verification failed: macaroon not found in storage") 283 return 284 } 285 c.Fatal("timed out waiting for storage expiry") 286 }