Skip to content

Commit

Permalink
controller: add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
vaijab committed Jun 7, 2017
1 parent 0cd9d93 commit bf6dbdb
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 21 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@
/etcd_ca.key
/kube_ca.crt
/kube_ca.key
pkg/cloudprovider/providers/aws/mocks/

pkg/**/mocks/
/mocks
2 changes: 2 additions & 0 deletions pkg/cloudprovider/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
51 changes: 31 additions & 20 deletions pkg/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
160 changes: 160 additions & 0 deletions pkg/controller/controller_test.go
Original file line number Diff line number Diff line change
@@ -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,
}
}

0 comments on commit bf6dbdb

Please sign in to comment.