package topology import ( "testing" ) func flagNode(ip string) *DataNode { dn := NewDataNode(ip) dn.Ip, dn.Port = ip, 8080 return dn } func TestLocationFlagsFollowTheirNode(t *testing.T) { a, b, c := flagNode("10.0.0.1"), flagNode("10.0.0.2"), flagNode("10.0.0.3") list := NewVolumeLocationList() for _, dn := range []*DataNode{a, b, c} { list.Set(dn) } list.SetReadOnly(b, true) list.SetOversized(c, true) if !list.AnyReadOnly() || !list.AnyOversized() { t.Fatal("flags did not register") } // Removing an earlier node shifts the rest down; the flags have to move with // them or they end up describing the wrong server. list.Remove(a) list.SetReadOnly(b, false) if list.AnyReadOnly() { t.Error("clearing the flag on its node left it set, so it had shifted onto another") } if !list.AnyOversized() { t.Error("removing an unrelated node dropped another node's flag") } list.SetOversized(c, false) if list.AnyOversized() { t.Error("clearing the flag on its node left it set") } } func TestLocationFlagsClearedWithTheirNode(t *testing.T) { a, b := flagNode("10.0.0.1"), flagNode("10.0.0.2") list := NewVolumeLocationList() list.Set(a) list.Set(b) list.SetReadOnly(a, true) list.Remove(a) if list.AnyReadOnly() { t.Error("a departed node left its read-only marking behind") } } // A node that shares an address replaces the entry, so it inherits the slot. func TestLocationFlagsSurviveAReplacedNode(t *testing.T) { a, replacement := flagNode("10.0.0.1"), flagNode("10.0.0.1") list := NewVolumeLocationList() list.Set(a) list.SetReadOnly(a, true) list.Set(replacement) if !list.AnyReadOnly() { t.Error("replacing the node at an address dropped what was known about the volume there") } list.SetReadOnly(replacement, false) if list.AnyReadOnly() { t.Error("the replacement could not clear the flag it inherited") } } func TestLocationFlagsIgnoreUntrackableReplicaCounts(t *testing.T) { list := NewVolumeLocationList() nodes := make([]*DataNode, 0, maxTrackedLocations+2) for i := 0; i < maxTrackedLocations+2; i++ { dn := NewDataNode("n") dn.Ip, dn.Port = "10.0.0.1", 9000+i nodes = append(nodes, dn) list.Set(dn) } // Past the tracked width the flag is not recorded rather than landing on // some other node's slot. list.SetReadOnly(nodes[maxTrackedLocations+1], true) if list.AnyReadOnly() { t.Error("a flag beyond the tracked width was recorded against another node") } list.SetReadOnly(nodes[0], true) if !list.AnyReadOnly() { t.Error("a flag within the tracked width was lost") } } // Refresh drops stale locations, so it has to rebuild the flags with them. func TestLocationFlagsRebuiltByRefresh(t *testing.T) { stale, fresh, alsoFresh := flagNode("10.0.0.1"), flagNode("10.0.0.2"), flagNode("10.0.0.3") stale.LastSeen, fresh.LastSeen, alsoFresh.LastSeen = 100, 500, 500 list := NewVolumeLocationList() for _, dn := range []*DataNode{stale, fresh, alsoFresh} { list.Set(dn) } list.SetReadOnly(alsoFresh, true) list.Refresh(400) if list.Length() != 2 { t.Fatalf("expected the stale location to be dropped, got %d", list.Length()) } // Both halves are needed: that the flag survived at all, and that it // survived against the right location. Rebuilding the mask as empty would // pass the second on its own. if !list.AnyReadOnly() { t.Fatal("the flag was dropped rather than carried across the rebuild") } list.SetReadOnly(alsoFresh, false) if list.AnyReadOnly() { t.Error("the flag did not move with its location, so it now describes another server") } }