From bf6dbdbb2550fba7f1d7b432489ad60eb5cf9856 Mon Sep 17 00:00:00 2001 From: Vaidas Jablonskis Date: Wed, 7 Jun 2017 11:25:47 +0100 Subject: [PATCH] controller: add unit tests --- .gitignore | 4 +- pkg/cloudprovider/cloud.go | 2 + pkg/controller/controller.go | 51 ++++++---- pkg/controller/controller_test.go | 160 ++++++++++++++++++++++++++++++ 4 files changed, 196 insertions(+), 21 deletions(-) create mode 100644 pkg/controller/controller_test.go diff --git a/.gitignore b/.gitignore index c4ffeed..0c6cb6d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ /etcd_ca.key /kube_ca.crt /kube_ca.key -pkg/cloudprovider/providers/aws/mocks/ + +pkg/**/mocks/ +/mocks diff --git a/pkg/cloudprovider/cloud.go b/pkg/cloudprovider/cloud.go index 71cd4cc..288c66a 100644 --- a/pkg/cloudprovider/cloud.go +++ b/pkg/cloudprovider/cloud.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//go:generate mockery -dir $GOPATH/src/github.com/UKHomeOffice/keto/pkg/cloudprovider -all + package cloudprovider import ( diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 2a7c76f..14a2625 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -29,6 +29,12 @@ import ( var ( // ErrNotImplemented is an error for not implemented features. ErrNotImplemented = errors.New("not implemented") + // ErrClusterAlreadyExists is an error to report an existing cluster. + ErrClusterAlreadyExists = errors.New("cluster already exists") + // ErrClusterDoesNotExist is an error to report a non-existing cluster. + ErrClusterDoesNotExist = errors.New("cluster does not exist") + // ErrMasterPoolAlreadyExists is an error to report an existing master pool. + ErrMasterPoolAlreadyExists = errors.New("masterpool already exists") ) // Controller represents a controller. @@ -39,7 +45,7 @@ type Controller struct { // Config represents a controller configuration. type Config struct { Cloud cloudprovider.Interface - UserData *userdata.UserData + UserData userdata.UserDater } // Validate validates controller configuration. @@ -64,6 +70,14 @@ func (c *Controller) CreateCluster(cluster model.Cluster, assets model.Assets) e return ErrNotImplemented } + exists, err := c.clusterExists(cluster.Name, cl) + if err != nil { + return err + } + if exists { + return ErrClusterAlreadyExists + } + if err := cl.CreateClusterInfra(cluster); err != nil { return err } @@ -93,16 +107,20 @@ func (c *Controller) CreateMasterPool(p model.MasterPool) error { return ErrNotImplemented } - // Check if cluster exists first. - if exists, err := c.clusterExists(p.ClusterName, cl); !exists { + exists, err := c.clusterExists(p.ClusterName, cl) + if err != nil { return err } + if !exists { + return ErrClusterDoesNotExist + } + m, err := c.GetMasterPools(p.ClusterName, "") if err != nil { return err } if len(m) != 0 { - return fmt.Errorf("masterpool already exists in cluster %q", p.ClusterName) + return ErrMasterPoolAlreadyExists } // Use defaults if values aren't specified. @@ -135,12 +153,9 @@ func (c *Controller) CreateMasterPool(p model.MasterPool) error { } func (c *Controller) clusterExists(name string, cl cloudprovider.Clusters) (bool, error) { - if c, err := cl.GetClusters(name); len(c) == 0 { - errMsg := "cluster not found" - if err != nil { - errMsg = fmt.Sprintf("%s: %v", errMsg, err) - } - return false, errors.New(errMsg) + clusters, err := cl.GetClusters(name) + if err != nil || len(clusters) != 1 { + return false, nil } return true, nil } @@ -201,14 +216,10 @@ func (c *Controller) CreateComputePool(p model.ComputePool) error { func (c *Controller) computePoolExists(clusterName, name string, pooler cloudprovider.NodePooler) (bool, error) { p, err := pooler.GetComputePools(clusterName, name) - if len(p) != 0 && err == nil { - return true, nil + if err != nil || len(p) == 0 { + return false, err } - - if err != nil { - return false, fmt.Errorf("unable to get compute pools: %v", err) - } - return false, nil + return true, nil } // GetMasterPools returns a list of master pools @@ -248,7 +259,7 @@ func (c *Controller) GetClusters(name string) ([]*model.Cluster, error) { return cl.GetClusters(name) } -// DeleteCluster deletes a node pool. +// DeleteCluster deletes a cluster. func (c *Controller) DeleteCluster(name string) error { cl, impl := c.Cloud.Clusters() if !impl { @@ -258,7 +269,7 @@ func (c *Controller) DeleteCluster(name string) error { return cl.DeleteCluster(name) } -// DeleteMasterPool deletes a node pool. +// DeleteMasterPool deletes a master node pool. func (c *Controller) DeleteMasterPool(clusterName string) error { pooler, impl := c.Cloud.NodePooler() if !impl { @@ -268,7 +279,7 @@ func (c *Controller) DeleteMasterPool(clusterName string) error { return pooler.DeleteMasterPool(clusterName) } -// DeleteComputePool deletes a node pool. +// DeleteComputePool deletes a compute node pool. func (c *Controller) DeleteComputePool(clusterName, name string) error { pooler, impl := c.Cloud.NodePooler() if !impl { diff --git a/pkg/controller/controller_test.go b/pkg/controller/controller_test.go new file mode 100644 index 0000000..6dd3e58 --- /dev/null +++ b/pkg/controller/controller_test.go @@ -0,0 +1,160 @@ +/* +Copyright 2017 The Keto Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "testing" + + cloudProviderMocks "github.com/UKHomeOffice/keto/pkg/cloudprovider/mocks" + userdataMocks "github.com/UKHomeOffice/keto/pkg/userdata/mocks" + + "github.com/UKHomeOffice/keto/pkg/model" +) + +const cloudProviderName = "mock" + +type testMock struct { + Provider *cloudProviderMocks.Interface + Clusters *cloudProviderMocks.Clusters + NodePooler *cloudProviderMocks.NodePooler + Node *cloudProviderMocks.Node + UserData *userdataMocks.UserDater +} + +func TestCreateCluster(t *testing.T) { + m, ctrl := makeTestMock() + + persistentIPs := map[string]string{"node0": "1.1.1.1"} + cluster := model.Cluster{ + Name: "foo", + MasterPool: model.MasterPool{NodePool: makeNodePool("foo", "master")}, + } + + m.Clusters.On("GetClusters", cluster.Name).Return([]*model.Cluster{}, nil).Once() + m.Clusters.On("CreateClusterInfra", cluster).Return(nil) + m.Clusters.On("PushAssets", cluster.Name, model.Assets{}).Return(nil) + + // At this point the cluster infra already exists. + m.Clusters.On("GetClusters", cluster.Name).Return([]*model.Cluster{&cluster}, nil).Once() + m.NodePooler.On("GetMasterPools", cluster.Name, "").Return([]*model.MasterPool{}, nil) + m.Clusters.On("GetMasterPersistentIPs", cluster.Name).Return(persistentIPs, nil) + m.Provider.On("ProviderName").Return(cloudProviderName) + + m.UserData.On("RenderMasterCloudConfig", + cloudProviderName, + cluster.Name, + cluster.MasterPool.KubeVersion, + persistentIPs).Return(cluster.MasterPool.UserData, + nil) + + m.NodePooler.On("CreateMasterPool", cluster.MasterPool).Return(nil) + + if err := ctrl.CreateCluster(cluster, model.Assets{}); err != nil { + t.Error(err) + } + + m.Clusters.AssertExpectations(t) + m.UserData.AssertExpectations(t) + m.NodePooler.AssertExpectations(t) + m.Provider.AssertExpectations(t) +} + +func TestCreateClusterAlreadyExists(t *testing.T) { + m, ctrl := makeTestMock() + + cluster := model.Cluster{ + Name: "foo", + MasterPool: model.MasterPool{NodePool: makeNodePool("foo", "master")}, + } + + m.Clusters.On("GetClusters", cluster.Name).Return([]*model.Cluster{&cluster}, nil).Once() + + if err := ctrl.CreateCluster(cluster, model.Assets{}); err != ErrClusterAlreadyExists { + t.Errorf("wrong error; got %q; want %q", err, ErrClusterAlreadyExists) + } + + m.Clusters.AssertExpectations(t) +} + +func TestCreateMasterPoolAlreadyExists(t *testing.T) { + m, ctrl := makeTestMock() + + clusterName := "foo" + p := model.MasterPool{ + NodePool: makeNodePool(clusterName, "master"), + } + + m.Clusters.On("GetClusters", clusterName).Return([]*model.Cluster{&model.Cluster{Name: clusterName}}, nil).Once() + m.NodePooler.On("GetMasterPools", clusterName, "").Return([]*model.MasterPool{&p}, nil) + + if err := ctrl.CreateMasterPool(p); err != ErrMasterPoolAlreadyExists { + t.Errorf("wrong error; got %q; want %q", err, ErrMasterPoolAlreadyExists) + } + + m.Clusters.AssertExpectations(t) + m.NodePooler.AssertExpectations(t) +} + +func TestDeleteCluster(t *testing.T) { + m, ctrl := makeTestMock() + m.Clusters.On("DeleteCluster", "foo").Return(nil) + + if err := ctrl.DeleteCluster("foo"); err != nil { + t.Error(err) + } + + m.Clusters.AssertExpectations(t) +} + +func makeTestMock() (*testMock, *Controller) { + m := &testMock{ + Provider: &cloudProviderMocks.Interface{}, + Clusters: &cloudProviderMocks.Clusters{}, + NodePooler: &cloudProviderMocks.NodePooler{}, + Node: &cloudProviderMocks.Node{}, + UserData: &userdataMocks.UserDater{}, + } + + m.Provider.On("Clusters").Return(m.Clusters, true) + m.Provider.On("NodePooler").Return(m.NodePooler, true) + + ctrl := New(Config{Cloud: m.Provider, UserData: m.UserData}) + return m, ctrl +} + +func makeNodePool(clusterName, name string) model.NodePool { + meta := model.ResourceMeta{ + Name: name, + ClusterName: clusterName, + } + + spec := model.NodePoolSpec{ + KubeVersion: "v1.7.0", + MachineType: "tiny", + CoreOSVersion: "v1", + SSHKey: "s3cr3tkey", + DiskSize: 10, + Size: 1, + Networks: []string{"network0", "network1"}, + UserData: []byte("mocked userdata"), + } + + return model.NodePool{ + ResourceMeta: meta, + NodePoolSpec: spec, + } +}