package bitseq import ( "fmt" "math/rand" "os" "path/filepath" "testing" "time" "github.com/docker/docker/libnetwork/datastore" "github.com/docker/libkv/store" "github.com/docker/libkv/store/boltdb" ) var ( defaultPrefix = filepath.Join(os.TempDir(), "libnetwork", "test", "bitseq") ) func init() { boltdb.Register() } func randomLocalStore() (datastore.DataStore, error) { tmp, err := os.CreateTemp("", "libnetwork-") if err != nil { return nil, fmt.Errorf("Error creating temp file: %v", err) } if err := tmp.Close(); err != nil { return nil, fmt.Errorf("Error closing temp file: %v", err) } return datastore.NewDataStore(datastore.ScopeCfg{ Client: datastore.ScopeClientCfg{ Provider: "boltdb", Address: filepath.Join(defaultPrefix, filepath.Base(tmp.Name())), Config: &store.Config{ Bucket: "libnetwork", ConnectionTimeout: 3 * time.Second, }, }, }) } const blockLen = 32 // This one tests an allocation pattern which unveiled an issue in pushReservation // Specifically a failure in detecting when we are in the (B) case (the bit to set // belongs to the last block of the current sequence). Because of a bug, code // was assuming the bit belonged to a block in the middle of the current sequence. // Which in turn caused an incorrect allocation when requesting a bit which is not // in the first or last sequence block. func TestSetAnyInRange(t *testing.T) { numBits := uint64(8 * blockLen) hnd, err := NewHandle("", nil, "", numBits) if err != nil { t.Fatal(err) } if err := hnd.Set(0); err != nil { t.Fatal(err) } if err := hnd.Set(255); err != nil { t.Fatal(err) } o, err := hnd.SetAnyInRange(128, 255, false) if err != nil { t.Fatal(err) } if o != 128 { t.Fatalf("Unexpected ordinal: %d", o) } o, err = hnd.SetAnyInRange(128, 255, false) if err != nil { t.Fatal(err) } if o != 129 { t.Fatalf("Unexpected ordinal: %d", o) } o, err = hnd.SetAnyInRange(246, 255, false) if err != nil { t.Fatal(err) } if o != 246 { t.Fatalf("Unexpected ordinal: %d", o) } o, err = hnd.SetAnyInRange(246, 255, false) if err != nil { t.Fatal(err) } if o != 247 { t.Fatalf("Unexpected ordinal: %d", o) } } func TestRandomAllocateDeallocate(t *testing.T) { ds, err := randomLocalStore() if err != nil { t.Fatal(err) } numBits := int(16 * blockLen) hnd, err := NewHandle("bitseq-test/data/", ds, "test1", uint64(numBits)) if err != nil { t.Fatal(err) } defer func() { if err := hnd.Destroy(); err != nil { t.Fatal(err) } }() seed := time.Now().Unix() rng := rand.New(rand.NewSource(seed)) // Allocate all bits using a random pattern pattern := rng.Perm(numBits) for _, bit := range pattern { err := hnd.Set(uint64(bit)) if err != nil { t.Errorf("Unexpected failure on allocation of %d: %v.\nSeed: %d.\n%s", bit, err, seed, hnd) } } if unselected := hnd.Unselected(); unselected != 0 { t.Errorf("Expected full sequence. Instead found %d free bits. Seed: %d.\n%s", unselected, seed, hnd) } // Deallocate all bits using a random pattern pattern = rng.Perm(numBits) for _, bit := range pattern { err := hnd.Unset(uint64(bit)) if err != nil { t.Errorf("Unexpected failure on deallocation of %d: %v.\nSeed: %d.\n%s", bit, err, seed, hnd) } } if unselected := hnd.Unselected(); unselected != uint64(numBits) { t.Errorf("Expected full sequence. Instead found %d free bits. Seed: %d.\n%s", unselected, seed, hnd) } } func TestRetrieveFromStore(t *testing.T) { ds, err := randomLocalStore() if err != nil { t.Fatal(err) } numBits := int(8 * blockLen) hnd, err := NewHandle("bitseq-test/data/", ds, "test1", uint64(numBits)) if err != nil { t.Fatal(err) } // Allocate first half of the bits for i := 0; i < numBits/2; i++ { _, err := hnd.SetAny(false) if err != nil { t.Fatalf("Unexpected failure on allocation %d: %v\n%s", i, err, hnd) } } hnd0 := hnd.String() // Retrieve same handle hnd, err = NewHandle("bitseq-test/data/", ds, "test1", uint64(numBits)) if err != nil { t.Fatal(err) } hnd1 := hnd.String() if hnd1 != hnd0 { t.Fatalf("%v\n%v", hnd0, hnd1) } err = hnd.Destroy() if err != nil { t.Fatal(err) } } func testSetRollover(t *testing.T, serial bool) { ds, err := randomLocalStore() if err != nil { t.Fatal(err) } numBlocks := uint32(8) numBits := int(numBlocks * blockLen) hnd, err := NewHandle("bitseq-test/data/", ds, "test1", uint64(numBits)) if err != nil { t.Fatal(err) } // Allocate first half of the bits for i := 0; i < numBits/2; i++ { _, err := hnd.SetAny(serial) if err != nil { t.Fatalf("Unexpected failure on allocation %d: %v\n%s", i, err, hnd) } } if unselected := hnd.Unselected(); unselected != uint64(numBits/2) { t.Fatalf("Expected full sequence. Instead found %d free bits. %s", unselected, hnd) } seed := time.Now().Unix() rng := rand.New(rand.NewSource(seed)) // Deallocate half of the allocated bits following a random pattern pattern := rng.Perm(numBits / 2) for i := 0; i < numBits/4; i++ { bit := pattern[i] err := hnd.Unset(uint64(bit)) if err != nil { t.Fatalf("Unexpected failure on deallocation of %d: %v.\nSeed: %d.\n%s", bit, err, seed, hnd) } } if unselected := hnd.Unselected(); unselected != uint64(3*numBits/4) { t.Fatalf("Unexpected free bits: found %d free bits.\nSeed: %d.\n%s", unselected, seed, hnd) } //request to allocate for remaining half of the bits for i := 0; i < numBits/2; i++ { _, err := hnd.SetAny(serial) if err != nil { t.Fatalf("Unexpected failure on allocation %d: %v\nSeed: %d\n%s", i, err, seed, hnd) } } //At this point all the bits must be allocated except the randomly unallocated bits //which were unallocated in the first half of the bit sequence if unselected := hnd.Unselected(); unselected != uint64(numBits/4) { t.Fatalf("Unexpected number of unselected bits %d, Expected %d", unselected, numBits/4) } for i := 0; i < numBits/4; i++ { _, err := hnd.SetAny(serial) if err != nil { t.Fatalf("Unexpected failure on allocation %d: %v\nSeed: %d\n%s", i, err, seed, hnd) } } //Now requesting to allocate the unallocated random bits (qurter of the number of bits) should //leave no more bits that can be allocated. if hnd.Unselected() != 0 { t.Fatalf("Unexpected number of unselected bits %d, Expected %d", hnd.Unselected(), 0) } err = hnd.Destroy() if err != nil { t.Fatal(err) } } func TestSetRollover(t *testing.T) { testSetRollover(t, false) } func TestSetRolloverSerial(t *testing.T) { testSetRollover(t, true) } func TestMarshalJSON(t *testing.T) { const expectedID = "my-bitseq" expected := []byte("hello libnetwork") hnd, err := NewHandle("", nil, expectedID, uint64(len(expected)*8)) if err != nil { t.Fatal(err) } for i, c := range expected { for j := 0; j < 8; j++ { if c&(1<