Bläddra i källkod

Create SetMatrix data structure

SetMatrix is a simple matrix of sets.
Added tests

This data structure will be used in following commit to handle
transient states where the same key can momentarely be associated
to more than a value

Signed-off-by: Flavio Crisciani <flavio.crisciani@docker.com>
Flavio Crisciani 8 år sedan
förälder
incheckning
2111d4cafb
3 ändrade filer med 270 tillägg och 0 borttagningar
  1. 1 0
      libnetwork/Makefile
  2. 123 0
      libnetwork/common/setmatrix.go
  3. 146 0
      libnetwork/common/setmatrix_test.go

+ 1 - 0
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
 

+ 123 - 0
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
+}

+ 146 - 0
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)
+	}
+}