github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/multiwatcher/store_internal_test.go (about) 1 // Copyright 2019 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package multiwatcher 5 6 import ( 7 "container/list" 8 "fmt" 9 10 "github.com/juju/loggo" 11 jc "github.com/juju/testing/checkers" 12 gc "gopkg.in/check.v1" 13 14 "github.com/juju/juju/testing" 15 ) 16 17 var _ = gc.Suite(&storeSuite{}) 18 19 type storeSuite struct { 20 testing.BaseSuite 21 } 22 23 var StoreChangeMethodTests = []struct { 24 about string 25 change func(*store) 26 expectRevno int64 27 expectContents []entityEntry 28 }{{ 29 about: "empty at first", 30 change: func(*store) {}, 31 }, { 32 about: "add single entry", 33 change: func(all *store) { 34 all.Update(&MachineInfo{ 35 ID: "0", 36 InstanceID: "i-0", 37 }) 38 }, 39 expectRevno: 1, 40 expectContents: []entityEntry{{ 41 creationRevno: 1, 42 revno: 1, 43 info: &MachineInfo{ 44 ID: "0", 45 InstanceID: "i-0", 46 }, 47 }}, 48 }, { 49 about: "add two entries", 50 change: func(all *store) { 51 all.Update(&MachineInfo{ 52 ID: "0", 53 InstanceID: "i-0", 54 }) 55 all.Update(&ApplicationInfo{ 56 Name: "wordpress", 57 Exposed: true, 58 }) 59 }, 60 expectRevno: 2, 61 expectContents: []entityEntry{{ 62 creationRevno: 1, 63 revno: 1, 64 info: &MachineInfo{ 65 ID: "0", 66 InstanceID: "i-0", 67 }, 68 }, { 69 creationRevno: 2, 70 revno: 2, 71 info: &ApplicationInfo{ 72 Name: "wordpress", 73 Exposed: true, 74 }, 75 }}, 76 }, { 77 about: "update an entity that's not currently there", 78 change: func(all *store) { 79 m := &MachineInfo{ID: "1"} 80 all.Update(m) 81 }, 82 expectRevno: 1, 83 expectContents: []entityEntry{{ 84 creationRevno: 1, 85 revno: 1, 86 info: &MachineInfo{ID: "1"}, 87 }}, 88 }, { 89 about: "mark application removed then update", 90 change: func(all *store) { 91 all.Update(&ApplicationInfo{ModelUUID: "uuid0", Name: "logging"}) 92 all.Update(&ApplicationInfo{ModelUUID: "uuid0", Name: "wordpress"}) 93 StoreIncRef(all, EntityID{"application", "uuid0", "logging"}) 94 all.Remove(EntityID{"application", "uuid0", "logging"}) 95 all.Update(&ApplicationInfo{ 96 ModelUUID: "uuid0", 97 Name: "wordpress", 98 Exposed: true, 99 }) 100 all.Update(&ApplicationInfo{ 101 ModelUUID: "uuid0", 102 Name: "logging", 103 Exposed: true, 104 }) 105 }, 106 expectRevno: 5, 107 expectContents: []entityEntry{{ 108 revno: 4, 109 creationRevno: 2, 110 removed: false, 111 refCount: 0, 112 info: &ApplicationInfo{ 113 ModelUUID: "uuid0", 114 Name: "wordpress", 115 Exposed: true, 116 }}, { 117 revno: 5, 118 creationRevno: 1, 119 removed: false, 120 refCount: 1, 121 info: &ApplicationInfo{ 122 ModelUUID: "uuid0", 123 Name: "logging", 124 Exposed: true, 125 }, 126 }}, 127 }, { 128 about: "mark removed on existing entry", 129 change: func(all *store) { 130 all.Update(&MachineInfo{ModelUUID: "uuid", ID: "0"}) 131 all.Update(&MachineInfo{ModelUUID: "uuid", ID: "1"}) 132 StoreIncRef(all, EntityID{"machine", "uuid", "0"}) 133 all.Remove(EntityID{"machine", "uuid", "0"}) 134 }, 135 expectRevno: 3, 136 expectContents: []entityEntry{{ 137 creationRevno: 2, 138 revno: 2, 139 info: &MachineInfo{ModelUUID: "uuid", ID: "1"}, 140 }, { 141 creationRevno: 1, 142 revno: 3, 143 refCount: 1, 144 removed: true, 145 info: &MachineInfo{ModelUUID: "uuid", ID: "0"}, 146 }}, 147 }, { 148 about: "mark removed on nonexistent entry", 149 change: func(all *store) { 150 all.Remove(EntityID{"machine", "uuid", "0"}) 151 }, 152 }, { 153 about: "mark removed on already marked entry", 154 change: func(all *store) { 155 all.Update(&MachineInfo{ModelUUID: "uuid", ID: "0"}) 156 all.Update(&MachineInfo{ModelUUID: "uuid", ID: "1"}) 157 StoreIncRef(all, EntityID{"machine", "uuid", "0"}) 158 all.Remove(EntityID{"machine", "uuid", "0"}) 159 all.Update(&MachineInfo{ 160 ModelUUID: "uuid", 161 ID: "1", 162 InstanceID: "i-1", 163 }) 164 all.Remove(EntityID{"machine", "uuid", "0"}) 165 }, 166 expectRevno: 4, 167 expectContents: []entityEntry{{ 168 creationRevno: 1, 169 revno: 3, 170 refCount: 1, 171 removed: true, 172 info: &MachineInfo{ModelUUID: "uuid", ID: "0"}, 173 }, { 174 creationRevno: 2, 175 revno: 4, 176 info: &MachineInfo{ 177 ModelUUID: "uuid", 178 ID: "1", 179 InstanceID: "i-1", 180 }, 181 }}, 182 }, { 183 about: "mark removed on entry with zero ref count", 184 change: func(all *store) { 185 all.Update(&MachineInfo{ModelUUID: "uuid", ID: "0"}) 186 all.Remove(EntityID{"machine", "uuid", "0"}) 187 }, 188 expectRevno: 2, 189 }, { 190 about: "delete entry", 191 change: func(all *store) { 192 all.Update(&MachineInfo{ModelUUID: "uuid", ID: "0"}) 193 all.delete(EntityID{"machine", "uuid", "0"}) 194 }, 195 expectRevno: 1, 196 }, { 197 about: "decref of non-removed entity", 198 change: func(all *store) { 199 m := &MachineInfo{ID: "0"} 200 all.Update(m) 201 id := m.EntityID() 202 StoreIncRef(all, id) 203 entry := all.entities[id].Value.(*entityEntry) 204 all.decRef(entry) 205 }, 206 expectRevno: 1, 207 expectContents: []entityEntry{{ 208 creationRevno: 1, 209 revno: 1, 210 refCount: 0, 211 info: &MachineInfo{ID: "0"}, 212 }}, 213 }, { 214 about: "decref of removed entity", 215 change: func(all *store) { 216 m := &MachineInfo{ID: "0"} 217 all.Update(m) 218 id := m.EntityID() 219 entry := all.entities[id].Value.(*entityEntry) 220 entry.refCount++ 221 all.Remove(id) 222 all.decRef(entry) 223 }, 224 expectRevno: 2, 225 }, 226 } 227 228 func (s *storeSuite) TestStoreChangeMethods(c *gc.C) { 229 for i, test := range StoreChangeMethodTests { 230 all := newStore(loggo.GetLogger("test")) 231 c.Logf("test %d. %s", i, test.about) 232 test.change(all) 233 assertStoreContents(c, all, test.expectRevno, test.expectContents) 234 } 235 } 236 237 func (s *storeSuite) TestChangesSince(c *gc.C) { 238 a := newStore(loggo.GetLogger("test")) 239 // Add three entries. 240 var deltas []Delta 241 for i := 0; i < 3; i++ { 242 m := &MachineInfo{ 243 ModelUUID: "uuid", 244 ID: fmt.Sprint(i), 245 } 246 a.Update(m) 247 deltas = append(deltas, Delta{Entity: m}) 248 } 249 // Check that the deltas from each revno are as expected. 250 for i := 0; i < 3; i++ { 251 c.Logf("test %d", i) 252 changes, _ := a.ChangesSince(int64(i)) 253 c.Assert(len(changes), gc.Equals, len(deltas)-i) 254 c.Assert(changes, jc.DeepEquals, deltas[i:]) 255 } 256 257 // Check boundary cases. 258 changes, _ := a.ChangesSince(-1) 259 c.Assert(changes, jc.DeepEquals, deltas) 260 changes, rev := a.ChangesSince(99) 261 c.Assert(changes, gc.HasLen, 0) 262 263 // Update one machine and check we see the changes. 264 m1 := &MachineInfo{ 265 ModelUUID: "uuid", 266 ID: "1", 267 InstanceID: "foo", 268 } 269 a.Update(m1) 270 changes, latest := a.ChangesSince(rev) 271 c.Assert(changes, jc.DeepEquals, []Delta{{Entity: m1}}) 272 c.Assert(latest, gc.Equals, a.latestRevno) 273 274 // Make sure the machine isn't simply removed from 275 // the list when it's marked as removed. 276 StoreIncRef(a, EntityID{"machine", "uuid", "0"}) 277 278 // Remove another machine and check we see it's removed. 279 m0 := &MachineInfo{ModelUUID: "uuid", ID: "0"} 280 a.Remove(m0.EntityID()) 281 282 // Check that something that never saw m0 does not get 283 // informed of its removal (even those the removed entity 284 // is still in the list. 285 changes, _ = a.ChangesSince(0) 286 c.Assert(changes, jc.DeepEquals, []Delta{{ 287 Entity: &MachineInfo{ModelUUID: "uuid", ID: "2"}, 288 }, { 289 Entity: m1, 290 }}) 291 292 changes, _ = a.ChangesSince(rev) 293 c.Assert(changes, jc.DeepEquals, []Delta{{ 294 Entity: m1, 295 }, { 296 Removed: true, 297 Entity: m0, 298 }}) 299 300 changes, _ = a.ChangesSince(rev + 1) 301 c.Assert(changes, jc.DeepEquals, []Delta{{ 302 Removed: true, 303 Entity: m0, 304 }}) 305 } 306 307 func (s *storeSuite) TestGet(c *gc.C) { 308 a := newStore(loggo.GetLogger("test")) 309 m := &MachineInfo{ModelUUID: "uuid", ID: "0"} 310 a.Update(m) 311 312 c.Assert(a.Get(m.EntityID()), gc.DeepEquals, m) 313 c.Assert(a.Get(EntityID{"machine", "uuid", "1"}), gc.IsNil) 314 } 315 316 func (s *storeSuite) TestDecReferenceWithZero(c *gc.C) { 317 // If a watcher is stopped before it had looked at any items, then we shouldn't 318 // decrement its ref count when it is stopped. 319 store := newStore(loggo.GetLogger("test")) 320 m := &MachineInfo{ModelUUID: "uuid", ID: "0"} 321 store.Update(m) 322 323 StoreIncRef(store, EntityID{"machine", "uuid", "0"}) 324 store.DecReference(0) 325 326 assertStoreContents(c, store, 1, []entityEntry{{ 327 creationRevno: 1, 328 revno: 1, 329 refCount: 1, 330 info: m, 331 }}) 332 } 333 334 func (s *storeSuite) TestDecReferenceIfAlreadySeenRemoved(c *gc.C) { 335 // If the Multiwatcher has already seen the item removed, then 336 // we shouldn't decrement its ref count when it is stopped. 337 338 store := newStore(loggo.GetLogger("test")) 339 m := &MachineInfo{ModelUUID: "uuid", ID: "0"} 340 store.Update(m) 341 342 id := EntityID{"machine", "uuid", "0"} 343 StoreIncRef(store, id) 344 store.Remove(id) 345 store.DecReference(0) 346 347 assertStoreContents(c, store, 2, []entityEntry{{ 348 creationRevno: 1, 349 revno: 2, 350 refCount: 1, 351 removed: true, 352 info: m, 353 }}) 354 } 355 356 func (s *storeSuite) TestHandleStopDecRefIfAlreadySeenAndNotRemoved(c *gc.C) { 357 // If the Multiwatcher has already seen the item removed, then 358 // we should decrement its ref count when it is stopped. 359 store := newStore(loggo.GetLogger("test")) 360 info := &MachineInfo{ModelUUID: "uuid", ID: "0"} 361 store.Update(info) 362 363 StoreIncRef(store, EntityID{"machine", "uuid", "0"}) 364 store.DecReference(store.latestRevno) 365 366 assertStoreContents(c, store, 1, []entityEntry{{ 367 creationRevno: 1, 368 revno: 1, 369 info: info, 370 }}) 371 } 372 373 func StoreIncRef(a *store, id interface{}) { 374 entry := a.entities[id].Value.(*entityEntry) 375 entry.refCount++ 376 } 377 378 func assertStoreContents(c *gc.C, a *store, latestRevno int64, entries []entityEntry) { 379 var gotEntries []entityEntry 380 var gotElems []*list.Element 381 c.Check(a.list.Len(), gc.Equals, len(entries)) 382 for e := a.list.Back(); e != nil; e = e.Prev() { 383 gotEntries = append(gotEntries, *e.Value.(*entityEntry)) 384 gotElems = append(gotElems, e) 385 } 386 c.Assert(gotEntries, jc.DeepEquals, entries) 387 for i, ent := range entries { 388 c.Assert(a.entities[ent.info.EntityID()], gc.Equals, gotElems[i]) 389 } 390 c.Assert(a.entities, gc.HasLen, len(entries)) 391 c.Assert(a.latestRevno, gc.Equals, latestRevno) 392 }