diff --git a/libnetwork/Makefile b/libnetwork/Makefile index 2a94e1d78b..c44fceb12d 100644 --- a/libnetwork/Makefile +++ b/libnetwork/Makefile @@ -121,6 +121,7 @@ run-tests: check-local: check-format check-code run-tests + integration-tests: ./bin/dnet @./test/integration/dnet/run-integration-tests.sh diff --git a/libnetwork/common/setmatrix.go b/libnetwork/common/setmatrix.go new file mode 100644 index 0000000000..0fdb542be4 --- /dev/null +++ b/libnetwork/common/setmatrix.go @@ -0,0 +1,123 @@ +package common + +import ( + "sync" + + mapset "github.com/deckarep/golang-set" +) + +// SetMatrix is a map of Sets +type SetMatrix interface { + // Get returns the members of the set for a specific key as a slice. + Get(key string) ([]interface{}, bool) + // Contains is used to verify is an element is in a set for a specific key + // returns true if the element is in the set + // returns true if there is a set for the key + Contains(key string, value interface{}) (bool, bool) + // Insert inserts the mapping between the IP and the endpoint identifier + // returns true if the mapping was not present, false otherwise + // returns also the number of endpoints associated to the IP + Insert(key string, value interface{}) (bool, int) + // Remove removes the mapping between the IP and the endpoint identifier + // returns true if the mapping was deleted, false otherwise + // returns also the number of endpoints associated to the IP + Remove(key string, value interface{}) (bool, int) + // Cardinality returns the number of elements in the set of a specfic key + // returns false if the key is not in the map + Cardinality(key string) (int, bool) + // String returns the string version of the set, empty otherwise + // returns false if the key is not in the map + String(key string) (string, bool) +} + +type setMatrix struct { + matrix map[string]mapset.Set + + sync.Mutex +} + +// NewSetMatrix creates a new set matrix object +func NewSetMatrix() SetMatrix { + s := &setMatrix{} + s.init() + return s +} + +func (s *setMatrix) init() { + s.matrix = make(map[string]mapset.Set) +} + +func (s *setMatrix) Get(key string) ([]interface{}, bool) { + s.Lock() + defer s.Unlock() + set, ok := s.matrix[key] + if !ok { + return nil, ok + } + return set.ToSlice(), ok +} + +func (s *setMatrix) Contains(key string, value interface{}) (bool, bool) { + s.Lock() + defer s.Unlock() + set, ok := s.matrix[key] + if !ok { + return false, ok + } + return set.Contains(value), ok +} + +func (s *setMatrix) Insert(key string, value interface{}) (bool, int) { + s.Lock() + defer s.Unlock() + set, ok := s.matrix[key] + if !ok { + s.matrix[key] = mapset.NewSet() + s.matrix[key].Add(value) + return true, 1 + } + + return set.Add(value), set.Cardinality() +} + +func (s *setMatrix) Remove(key string, value interface{}) (bool, int) { + s.Lock() + defer s.Unlock() + set, ok := s.matrix[key] + if !ok { + return false, 0 + } + + var removed bool + if set.Contains(value) { + set.Remove(value) + removed = true + // If the set is empty remove it from the matrix + if set.Cardinality() == 0 { + delete(s.matrix, key) + } + } + + return removed, set.Cardinality() +} + +func (s *setMatrix) Cardinality(key string) (int, bool) { + s.Lock() + defer s.Unlock() + set, ok := s.matrix[key] + if !ok { + return 0, ok + } + + return set.Cardinality(), ok +} + +func (s *setMatrix) String(key string) (string, bool) { + s.Lock() + defer s.Unlock() + set, ok := s.matrix[key] + if !ok { + return "", ok + } + return set.String(), ok +} diff --git a/libnetwork/common/setmatrix_test.go b/libnetwork/common/setmatrix_test.go new file mode 100644 index 0000000000..d87ffc7dfe --- /dev/null +++ b/libnetwork/common/setmatrix_test.go @@ -0,0 +1,146 @@ +package common + +import ( + "context" + "strconv" + "testing" + "time" + + _ "github.com/docker/libnetwork/testutils" +) + +func TestSetSerialInsertDelete(t *testing.T) { + s := NewSetMatrix() + + b, i := s.Insert("a", "1") + if !b || i != 1 { + t.Fatalf("error in insert %t %d", b, i) + } + b, i = s.Insert("a", "1") + if b || i != 1 { + t.Fatalf("error in insert %t %d", b, i) + } + b, i = s.Insert("a", "2") + if !b || i != 2 { + t.Fatalf("error in insert %t %d", b, i) + } + b, i = s.Insert("a", "1") + if b || i != 2 { + t.Fatalf("error in insert %t %d", b, i) + } + b, i = s.Insert("a", "3") + if !b || i != 3 { + t.Fatalf("error in insert %t %d", b, i) + } + b, i = s.Insert("a", "2") + if b || i != 3 { + t.Fatalf("error in insert %t %d", b, i) + } + b, i = s.Insert("a", "3") + if b || i != 3 { + t.Fatalf("error in insert %t %d", b, i) + } + b, i = s.Insert("a", "4") + if !b || i != 4 { + t.Fatalf("error in insert %t %d", b, i) + } + + b, p := s.Contains("a", "1") + if !b || !p { + t.Fatalf("error in contains %t %t", b, p) + } + b, p = s.Contains("a", "2") + if !b || !p { + t.Fatalf("error in contains %t %t", b, p) + } + b, p = s.Contains("a", "3") + if !b || !p { + t.Fatalf("error in contains %t %t", b, p) + } + b, p = s.Contains("a", "4") + if !b || !p { + t.Fatalf("error in contains %t %t", b, p) + } + + i, b = s.Cardinality("a") + if !b || i != 4 { + t.Fatalf("error in cardinality count %t %d", b, i) + } + + b, i = s.Remove("a", "1") + if !b || i != 3 { + t.Fatalf("error in remove %t %d", b, i) + } + b, i = s.Remove("a", "3") + if !b || i != 2 { + t.Fatalf("error in remove %t %d", b, i) + } + b, i = s.Remove("a", "1") + if b || i != 2 { + t.Fatalf("error in remove %t %d", b, i) + } + b, i = s.Remove("a", "4") + if !b || i != 1 { + t.Fatalf("error in remove %t %d", b, i) + } + b, i = s.Remove("a", "2") + if !b || i != 0 { + t.Fatalf("error in remove %t %d", b, i) + } + b, i = s.Remove("a", "2") + if b || i != 0 { + t.Fatalf("error in remove %t %d", b, i) + } + + i, b = s.Cardinality("a") + if b || i != 0 { + t.Fatalf("error in cardinality count %t %d", b, i) + } +} + +func insertDeleteRotuine(ctx context.Context, endCh chan int, s SetMatrix, key, value string) { + for { + select { + case <-ctx.Done(): + endCh <- 0 + return + default: + b, _ := s.Insert(key, value) + if !b { + endCh <- 1 + return + } + + b, _ = s.Remove(key, value) + if !b { + endCh <- 2 + return + } + } + } +} + +func TestSetParallelInsertDelete(t *testing.T) { + s := NewSetMatrix() + parallelRoutines := 6 + endCh := make(chan int) + // Let the routines running and competing for 10s + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + for i := 0; i < parallelRoutines; i++ { + go insertDeleteRotuine(ctx, endCh, s, "key-"+strconv.Itoa(i%3), strconv.Itoa(i)) + } + for parallelRoutines > 0 { + v := <-endCh + if v == 1 { + t.Fatalf("error one goroutine failed on the insert") + } + if v == 2 { + t.Fatalf("error one goroutine failed on the remove") + } + parallelRoutines-- + } + if i, b := s.Cardinality("key"); b || i > 0 { + t.Fatalf("error the set should be empty %t %d", b, i) + } +}