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>
This commit is contained in:
Flavio Crisciani 2017-06-07 13:09:50 -07:00
parent eb57059e91
commit 8783788123
No known key found for this signature in database
GPG Key ID: 28CAFCE754CF3A48
3 changed files with 270 additions and 0 deletions

View File

@ -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
common/setmatrix.go Normal file
View File

@ -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
common/setmatrix_test.go Normal file
View File

@ -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)
}
}