From 48be7163dc65da9b5e3da5026a8e49c8ec84bf4e Mon Sep 17 00:00:00 2001 From: Mikhail Podtserkovskiy Date: Tue, 11 Jul 2017 23:16:17 +0300 Subject: [PATCH] local storage + tests --- invoke.go | 3 + pool/strategy/persistent/nodeHelper_test.go | 9 +- storage/local/factory.go | 13 ++ storage/local/factory_test.go | 14 ++ storage/local/local_test.go | 151 ++++++++++++++++++++ storage/local/storage.go | 130 +++++++++++++++++ storage/storage.go | 2 +- 7 files changed, 316 insertions(+), 6 deletions(-) create mode 100644 storage/local/factory.go create mode 100644 storage/local/factory_test.go create mode 100644 storage/local/local_test.go create mode 100644 storage/local/storage.go diff --git a/invoke.go b/invoke.go index e79be16..56e136a 100644 --- a/invoke.go +++ b/invoke.go @@ -11,6 +11,7 @@ import ( "github.com/qa-dev/jsonwire-grid/pool/strategy/kubernetes" "github.com/qa-dev/jsonwire-grid/pool/strategy/persistent" "github.com/qa-dev/jsonwire-grid/selenium" + "github.com/qa-dev/jsonwire-grid/storage/local" "github.com/qa-dev/jsonwire-grid/storage/mysql" "github.com/qa-dev/jsonwire-grid/wda" ) @@ -27,6 +28,8 @@ func invokeStorageFactory(config config.Config) (factory StorageFactoryInterface switch config.DB.Implementation { case "mysql": factory = new(mysql.Factory) + case "local": + factory = new(local.Factory) default: err = errors.New("Invalid config, unknown param [db.implementation=" + config.DB.Implementation + "]") } diff --git a/pool/strategy/persistent/nodeHelper_test.go b/pool/strategy/persistent/nodeHelper_test.go index 401713a..f79c0dc 100644 --- a/pool/strategy/persistent/nodeHelper_test.go +++ b/pool/strategy/persistent/nodeHelper_test.go @@ -1,12 +1,12 @@ package persistent import ( - "testing" - "github.com/stretchr/testify/assert" - "github.com/qa-dev/jsonwire-grid/jsonwire" "encoding/json" - "github.com/stretchr/testify/mock" "errors" + "github.com/qa-dev/jsonwire-grid/jsonwire" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "testing" ) func TestNodeHelperFactory_create(t *testing.T) { @@ -93,4 +93,3 @@ func TestNodeHelper_removeAllSessions_Negative_CloseSession_MessageStatusNotOk(t _, err := nodeHelper.removeAllSessions() assert.NotNil(t, err) } - diff --git a/storage/local/factory.go b/storage/local/factory.go new file mode 100644 index 0000000..cb635ab --- /dev/null +++ b/storage/local/factory.go @@ -0,0 +1,13 @@ +package local + +import ( + "github.com/qa-dev/jsonwire-grid/config" + "github.com/qa-dev/jsonwire-grid/pool" +) + +type Factory struct { +} + +func (f *Factory) Create(cfg config.Config) (pool.StorageInterface, error) { + return &Storage{db: make(map[string]*pool.Node)}, nil +} diff --git a/storage/local/factory_test.go b/storage/local/factory_test.go new file mode 100644 index 0000000..488ff9a --- /dev/null +++ b/storage/local/factory_test.go @@ -0,0 +1,14 @@ +package local + +import ( + "github.com/qa-dev/jsonwire-grid/config" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestFactory_Create_Positive(t *testing.T) { + f := Factory{} + storage, err := f.Create(config.Config{}) + assert.NoError(t, err) + assert.NotNil(t, storage) +} diff --git a/storage/local/local_test.go b/storage/local/local_test.go new file mode 100644 index 0000000..e2c775a --- /dev/null +++ b/storage/local/local_test.go @@ -0,0 +1,151 @@ +package local + +import ( + "github.com/qa-dev/jsonwire-grid/pool" + "github.com/qa-dev/jsonwire-grid/storage" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestStorage_Add_Positive(t *testing.T) { + s := Storage{db: map[string]*pool.Node{}} + err := s.Add(pool.Node{}, 0) + assert.NoError(t, err) + assert.Len(t, s.db, 1) +} + +func TestStorage_Add_Positive_Repeat(t *testing.T) { + s := Storage{db: map[string]*pool.Node{"1": {Address: "1"}}} + err := s.Add(pool.Node{Address: "1"}, 0) + assert.NoError(t, err) + assert.Len(t, s.db, 1) +} + +func TestStorage_Add_Negative_LimitReached(t *testing.T) { + s := Storage{db: map[string]*pool.Node{"1": {Address: "1"}}} + limit := 1 + err := s.Add(pool.Node{Address: "2"}, limit) + assert.Error(t, err, "limit reached") + assert.Len(t, s.db, limit) +} + +func TestStorage_ReserveAvailable_Positive(t *testing.T) { + expectedNode := pool.Node{Address: "1", Status: pool.NodeStatusAvailable} + s := Storage{db: map[string]*pool.Node{expectedNode.Address: &expectedNode}} + node, err := s.ReserveAvailable([]pool.Node{expectedNode}) + assert.NoError(t, err) + assert.Equal(t, expectedNode, node) + assert.Equal(t, pool.NodeStatusReserved, s.db[node.Address].Status) +} + +func TestStorage_ReserveAvailable_Negative_NotFoundAvailableNodes(t *testing.T) { + expectedNode := pool.Node{Address: "1", Status: pool.NodeStatusBusy} + s := Storage{db: map[string]*pool.Node{expectedNode.Address: &expectedNode}} + _, err := s.ReserveAvailable([]pool.Node{expectedNode}) + assert.Error(t, err, storage.ErrNotFound) +} + +func TestStorage_ReserveAvailable_Negative_InvalidNodeList(t *testing.T) { + s := Storage{db: map[string]*pool.Node{}} + _, err := s.ReserveAvailable([]pool.Node{{Address: "awd"}}) + assert.Error(t, err, storage.ErrNotFound) +} + +func TestStorage_SetBusy_Positive(t *testing.T) { + expectedNode := pool.Node{Address: "1"} + s := Storage{db: map[string]*pool.Node{expectedNode.Address: &expectedNode}} + expectedSessionID := "expectedSessionID" + err := s.SetBusy(expectedNode, expectedSessionID) + assert.NoError(t, err) + assert.Equal(t, pool.NodeStatusBusy, s.db[expectedNode.Address].Status) + assert.Equal(t, expectedSessionID, s.db[expectedNode.Address].SessionID) +} + +func TestStorage_SetBusy_Negative(t *testing.T) { + expectedNode := pool.Node{Address: "1"} + s := Storage{db: map[string]*pool.Node{}} + expectedSessionID := "expectedSessionID" + err := s.SetBusy(expectedNode, expectedSessionID) + assert.Error(t, err, storage.ErrNotFound) +} + +func TestStorage_SetAvailable_Positive(t *testing.T) { + expectedNode := pool.Node{Address: "1"} + s := Storage{db: map[string]*pool.Node{expectedNode.Address: &expectedNode}} + err := s.SetAvailable(expectedNode) + assert.NoError(t, err) + assert.Equal(t, pool.NodeStatusAvailable, s.db[expectedNode.Address].Status) +} + +func TestStorage_SetAvailable_Negative(t *testing.T) { + expectedNode := pool.Node{Address: "1"} + s := Storage{db: map[string]*pool.Node{}} + err := s.SetAvailable(expectedNode) + assert.Error(t, err, storage.ErrNotFound) +} + +func TestStorage_GetCountWithStatus_Positive_All(t *testing.T) { + s := Storage{db: map[string]*pool.Node{"1": {Address: "1"}, "2": {Address: "2"}}} + count, err := s.GetCountWithStatus(nil) + assert.NoError(t, err) + assert.Equal(t, count, len(s.db)) +} + +func TestStorage_GetCountWithStatus_Positive_One(t *testing.T) { + expectedStatus := pool.NodeStatusBusy + s := Storage{db: map[string]*pool.Node{"1": {Address: "1", Status: expectedStatus}, "2": {Address: "2"}}} + count, err := s.GetCountWithStatus(&expectedStatus) + assert.NoError(t, err) + assert.Equal(t, count, 1) +} + +func TestStorage_GetBySession_Positive(t *testing.T) { + expectedNode := pool.Node{Address: "1"} + s := Storage{db: map[string]*pool.Node{expectedNode.Address: &expectedNode}} + node, err := s.GetBySession(expectedNode.SessionID) + assert.NoError(t, err) + assert.Equal(t, expectedNode, node) +} + +func TestStorage_GetBySession_Negative(t *testing.T) { + expectedNode := pool.Node{Address: "1"} + s := Storage{db: map[string]*pool.Node{}} + _, err := s.GetBySession(expectedNode.SessionID) + assert.Error(t, err, storage.ErrNotFound) +} + +func TestStorage_GetByAddress_Positive(t *testing.T) { + expectedNode := pool.Node{Address: "1"} + s := Storage{db: map[string]*pool.Node{expectedNode.Address: &expectedNode}} + node, err := s.GetByAddress(expectedNode.Address) + assert.NoError(t, err) + assert.Equal(t, expectedNode, node) +} + +func TestStorage_GetByAddress_Negative(t *testing.T) { + expectedNode := pool.Node{Address: "1"} + s := Storage{db: map[string]*pool.Node{}} + _, err := s.GetByAddress(expectedNode.Address) + assert.Error(t, err, storage.ErrNotFound) +} + +func TestStorage_GetAll_Positive(t *testing.T) { + s := Storage{db: map[string]*pool.Node{"1": {Address: "1"}, "2": {Address: "2"}}} + nodeList, err := s.GetAll() + assert.NoError(t, err) + assert.Len(t, nodeList, 2) +} + +func TestStorage_Remove_Positive(t *testing.T) { + node := pool.Node{Address: "1"} + s := Storage{db: map[string]*pool.Node{node.Address: &node}} + err := s.Remove(node) + assert.NoError(t, err) +} + +func TestStorage_Remove_Negative(t *testing.T) { + s := Storage{db: map[string]*pool.Node{}} + node := pool.Node{Address: "1"} + err := s.Remove(node) + assert.Error(t, err, storage.ErrNotFound) +} diff --git a/storage/local/storage.go b/storage/local/storage.go new file mode 100644 index 0000000..ff08859 --- /dev/null +++ b/storage/local/storage.go @@ -0,0 +1,130 @@ +package local + +import ( + "errors" + "github.com/qa-dev/jsonwire-grid/pool" + "github.com/qa-dev/jsonwire-grid/storage" + "sync" + "time" +) + +type Storage struct { + mu sync.RWMutex + db map[string]*pool.Node +} + +func (s *Storage) Add(node pool.Node, limit int) error { + s.mu.Lock() + defer s.mu.Unlock() + if limit > 0 { + i := 0 + for _, currNode := range s.db { + if currNode.Status == node.Status { + i++ + } + } + if i >= limit { + return errors.New("limit reached") + } + } + + s.db[node.Address] = &node + return nil +} + +func (s *Storage) ReserveAvailable(nodeList []pool.Node) (pool.Node, error) { + s.mu.Lock() + defer s.mu.Unlock() + for _, node := range nodeList { + dbNode, ok := s.db[node.Address] + if ok && dbNode.Status == pool.NodeStatusAvailable { + dbNode.Status = pool.NodeStatusReserved + dbNode.Updated = time.Now().Unix() + return *dbNode, nil + } + } + return pool.Node{}, storage.ErrNotFound +} + +func (s *Storage) SetBusy(node pool.Node, sessionID string) error { + s.mu.Lock() + defer s.mu.Unlock() + storedNode, ok := s.db[node.Address] + if !ok { + return storage.ErrNotFound + } + storedNode.Status = pool.NodeStatusBusy + storedNode.SessionID = sessionID + storedNode.Updated = time.Now().Unix() + return nil +} + +func (s *Storage) SetAvailable(node pool.Node) error { + s.mu.Lock() + defer s.mu.Unlock() + storedNode, ok := s.db[node.Address] + if !ok { + return storage.ErrNotFound + } + storedNode.Status = pool.NodeStatusAvailable + storedNode.Updated = time.Now().Unix() + return nil +} + +func (s *Storage) GetCountWithStatus(status *pool.NodeStatus) (int, error) { + s.mu.RLock() + defer s.mu.RUnlock() + if status == nil { + return len(s.db), nil + } + count := 0 + for _, node := range s.db { + if node.Status == *status { + count++ + } + } + return count, nil +} + +func (s *Storage) GetBySession(sessionID string) (pool.Node, error) { + s.mu.RLock() + defer s.mu.RUnlock() + for _, node := range s.db { + if node.SessionID == sessionID { + return *node, nil + } + } + return pool.Node{}, storage.ErrNotFound +} + +func (s *Storage) GetByAddress(address string) (pool.Node, error) { + s.mu.RLock() + defer s.mu.RUnlock() + node, ok := s.db[address] + if !ok { + return pool.Node{}, storage.ErrNotFound + } + return *node, nil +} + +func (s *Storage) GetAll() ([]pool.Node, error) { + s.mu.RLock() + defer s.mu.RUnlock() + nodeList := make([]pool.Node, 0, len(s.db)) + for _, value := range s.db { + nodeList = append(nodeList, *value) + } + + return nodeList, nil +} + +func (s *Storage) Remove(node pool.Node) error { + s.mu.Lock() + defer s.mu.Unlock() + _, ok := s.db[node.Address] + if !ok { + return storage.ErrNotFound + } + delete(s.db, node.Address) + return nil +} diff --git a/storage/storage.go b/storage/storage.go index 26d0aef..cd9ceb4 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -5,5 +5,5 @@ import ( ) var ( - ErrNotFound = errors.New("storage: not found available nodes") + ErrNotFound = errors.New("storage: node not found ") )