+ shouldPull: slots{2, 4},
+ expectBlockState: &balancedBlockState{
+ needed: 2,
+ pulling: 2,
+ }})
+}
+
+func (bal *balancerSuite) TestAllowTrashWhenReadOnly(c *check.C) {
+ srvs := bal.srvList(0, slots{3})
+ srvs[0].mounts[0].KeepMount.AllowWrite = false
+ srvs[0].mounts[0].KeepMount.AllowTrash = true
+ // can't pull to slot 3, so pull to slot 4 instead
+ bal.try(c, tester{
+ desired: map[string]int{"default": 4},
+ current: slots{0, 1},
+ shouldPull: slots{2, 4},
+ expectBlockState: &balancedBlockState{
+ needed: 2,
+ pulling: 2,
+ }})
+ // expect to be able to trash slot 3 in future, so pull to
+ // slot 1
+ bal.try(c, tester{
+ desired: map[string]int{"default": 2},
+ current: slots{0, 3},
+ shouldPull: slots{1},
+ expectBlockState: &balancedBlockState{
+ needed: 2,
+ pulling: 1,
+ }})
+ // trash excess from slot 3
+ bal.try(c, tester{
+ desired: map[string]int{"default": 2},
+ current: slots{0, 1, 3},
+ shouldTrash: slots{3},
+ expectBlockState: &balancedBlockState{
+ needed: 2,
+ unneeded: 1,
+ }})
+}
+
+func (bal *balancerSuite) TestMultipleViewsReadOnly(c *check.C) {
+ bal.testMultipleViews(c, false, false)
+}
+
+func (bal *balancerSuite) TestMultipleViewsReadOnlyAllowTrash(c *check.C) {
+ bal.testMultipleViews(c, false, true)
+}
+
+func (bal *balancerSuite) TestMultipleViews(c *check.C) {
+ bal.testMultipleViews(c, true, true)
+}
+
+func (bal *balancerSuite) testMultipleViews(c *check.C, allowWrite, allowTrash bool) {
+ for i, srv := range bal.srvs {
+ // Add a mount to each service
+ srv.mounts[0].KeepMount.DeviceID = fmt.Sprintf("writable-by-srv-%x", i)
+ srv.mounts = append(srv.mounts, &KeepMount{
+ KeepMount: arvados.KeepMount{
+ DeviceID: bal.srvs[(i+1)%len(bal.srvs)].mounts[0].KeepMount.DeviceID,
+ UUID: bal.srvs[(i+1)%len(bal.srvs)].mounts[0].KeepMount.UUID,
+ AllowWrite: allowWrite,
+ AllowTrash: allowTrash,
+ Replication: 1,
+ StorageClasses: map[string]bool{"default": true},
+ },
+ KeepService: srv,
+ })
+ }
+ for i := 1; i < len(bal.srvs); i++ {
+ c.Logf("i=%d", i)
+ if i == 4 {
+ // Timestamps are all different, but one of
+ // the mounts on srv[4] has the same device ID
+ // where the non-deletable replica is stored
+ // on srv[3], so only one replica is safe to
+ // trash.
+ bal.try(c, tester{
+ desired: map[string]int{"default": 1},
+ current: slots{0, i, i},
+ shouldTrash: slots{i}})
+ } else if !allowTrash {
+ // Timestamps are all different, and the third
+ // replica can't be trashed because it's on a
+ // read-only mount (with
+ // AllowTrashWhenReadOnly=false), so the first
+ // two replicas should be trashed.
+ bal.try(c, tester{
+ desired: map[string]int{"default": 1},
+ current: slots{0, i, i},
+ shouldTrash: slots{0, i}})
+ } else {
+ // Timestamps are all different, so both
+ // replicas on the non-optimal server should
+ // be trashed.
+ bal.try(c, tester{
+ desired: map[string]int{"default": 1},
+ current: slots{0, i, i},
+ shouldTrash: slots{i, i}})
+ }
+ // If the three replicas have identical timestamps,
+ // none of them can be trashed safely.
+ bal.try(c, tester{
+ desired: map[string]int{"default": 1},
+ current: slots{0, i, i},
+ timestamps: []int64{12345678, 12345678, 12345678}})
+ // If the first and third replicas have identical
+ // timestamps, only the second replica should be
+ // trashed.
+ bal.try(c, tester{
+ desired: map[string]int{"default": 1},
+ current: slots{0, i, i},
+ timestamps: []int64{12345678, 12345679, 12345678},
+ shouldTrash: slots{i}})
+ }