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  }