123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- 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<<j) == 0 {
- continue
- }
- if err := hnd.Set(uint64(i*8 + j)); err != nil {
- t.Fatal(err)
- }
- }
- }
- hstr := hnd.String()
- t.Log(hstr)
- marshaled, err := hnd.MarshalJSON()
- if err != nil {
- t.Fatalf("MarshalJSON() err = %v", err)
- }
- t.Logf("%s", marshaled)
- // Serializations of hnd as would be marshaled by versions of the code
- // found in the wild. We need to support unmarshaling old versions to
- // maintain backwards compatibility with sequences persisted on disk.
- const (
- goldenV0 = `{"id":"my-bitseq","sequence":"AAAAAAAAAIAAAAAAAAAAPRamNjYAAAAAAAAAAfYENpYAAAAAAAAAAUZ2pi4AAAAAAAAAAe72TtYAAAAAAAAAAQ=="}`
- )
- if string(marshaled) != goldenV0 {
- t.Errorf("MarshalJSON() output differs from golden. Please add a new golden case to this test.")
- }
- for _, tt := range []struct {
- name string
- data []byte
- }{
- {name: "Live", data: marshaled},
- {name: "Golden-v0", data: []byte(goldenV0)},
- } {
- tt := tt
- t.Run("UnmarshalJSON="+tt.name, func(t *testing.T) {
- hnd2, err := NewHandle("", nil, "", 0)
- if err != nil {
- t.Fatal(err)
- }
- if err := hnd2.UnmarshalJSON(tt.data); err != nil {
- t.Errorf("UnmarshalJSON() err = %v", err)
- }
- h2str := hnd2.String()
- t.Log(h2str)
- if hstr != h2str {
- t.Errorf("Unmarshaled a different bitseq: want %q, got %q", hstr, h2str)
- }
- })
- }
- }
|