diff --git a/cmd/cluster_create.go b/cmd/cluster_create.go index b3abaaef..582b1395 100644 --- a/cmd/cluster_create.go +++ b/cmd/cluster_create.go @@ -92,12 +92,12 @@ func RunClusterCreate(cmd *cobra.Command, args []string) { } if haEnabled && isolatedEtcd { - if err := hetznerProvider.CreateEtcdNodes(sshKeyName, masterServerType, datacenters, etcdCount); err != nil { + if _, err := hetznerProvider.CreateEtcdNodes(sshKeyName, masterServerType, datacenters, etcdCount); err != nil { log.Println(err) } } - if err := hetznerProvider.CreateMasterNodes(sshKeyName, masterServerType, datacenters, masterCount, !isolatedEtcd); err != nil { + if _, err := hetznerProvider.CreateMasterNodes(sshKeyName, masterServerType, datacenters, masterCount, !isolatedEtcd); err != nil { log.Println(err) } diff --git a/pkg/hetzner/hetzner_provider.go b/pkg/hetzner/hetzner_provider.go index e2b8df0e..93b5370a 100644 --- a/pkg/hetzner/hetzner_provider.go +++ b/pkg/hetzner/hetzner_provider.go @@ -29,7 +29,6 @@ type Provider struct { // NewHetznerProvider returns an instance of hetzner.Provider func NewHetznerProvider(context context.Context, client *hcloud.Client, token string) *Provider { - return &Provider{client: client, context: context, token: token} } @@ -127,29 +126,25 @@ func (provider *Provider) CreateNodes(suffix string, template clustermanager.Nod } // CreateEtcdNodes creates nodes with type 'etcd' -func (provider *Provider) CreateEtcdNodes(sshKeyName string, masterServerType string, datacenters []string, count int) error { +func (provider *Provider) CreateEtcdNodes(sshKeyName string, masterServerType string, datacenters []string, count int) ([]clustermanager.Node, error) { template := clustermanager.Node{SSHKeyName: sshKeyName, IsEtcd: true, Type: masterServerType} - _, err := provider.CreateNodes("etcd", template, datacenters, count, 0) - return err + return provider.CreateNodes("etcd", template, datacenters, count, 0) } // CreateMasterNodes creates nodes with type 'master' -func (provider *Provider) CreateMasterNodes(sshKeyName string, masterServerType string, datacenters []string, count int, isEtcd bool) error { +func (provider *Provider) CreateMasterNodes(sshKeyName string, masterServerType string, datacenters []string, count int, isEtcd bool) ([]clustermanager.Node, error) { template := clustermanager.Node{SSHKeyName: sshKeyName, IsMaster: true, Type: masterServerType, IsEtcd: isEtcd} - _, err := provider.CreateNodes("master", template, datacenters, count, 0) - return err + return provider.CreateNodes("master", template, datacenters, count, 0) } // CreateWorkerNodes create new worker node on provider func (provider *Provider) CreateWorkerNodes(sshKeyName string, workerServerType string, datacenters []string, count int, offset int) ([]clustermanager.Node, error) { template := clustermanager.Node{SSHKeyName: sshKeyName, IsMaster: false, Type: workerServerType} - nodes, err := provider.CreateNodes("worker", template, datacenters, count, offset) - return nodes, err + return provider.CreateNodes("worker", template, datacenters, count, offset) } // GetAllNodes retrieves all nodes func (provider *Provider) GetAllNodes() []clustermanager.Node { - return provider.nodes } @@ -160,59 +155,42 @@ func (provider *Provider) SetNodes(nodes []clustermanager.Node) { // GetMasterNodes returns master nodes only func (provider *Provider) GetMasterNodes() []clustermanager.Node { - nodes := []clustermanager.Node{} - for _, node := range provider.nodes { - if node.IsMaster { - nodes = append(nodes, node) - } - } - - return nodes + return provider.filterNodes(func(node clustermanager.Node) bool { + return node.IsMaster + }) } // GetEtcdNodes returns etcd nodes only func (provider *Provider) GetEtcdNodes() []clustermanager.Node { - - nodes := []clustermanager.Node{} - for _, node := range provider.nodes { - if node.IsEtcd { - nodes = append(nodes, node) - } - } - - return nodes + return provider.filterNodes(func(node clustermanager.Node) bool { + return node.IsEtcd + }) } // GetWorkerNodes returns worker nodes only func (provider *Provider) GetWorkerNodes() []clustermanager.Node { - nodes := []clustermanager.Node{} - for _, node := range provider.nodes { - if !node.IsMaster && !node.IsEtcd { - nodes = append(nodes, node) - } - } - - return nodes + return provider.filterNodes(func(node clustermanager.Node) bool { + return !node.IsMaster && !node.IsEtcd + }) } // GetMasterNode returns the first master node or fail, if no master nodes are found func (provider *Provider) GetMasterNode() (*clustermanager.Node, error) { - for _, node := range provider.nodes { - if node.IsMaster { - return &node, nil - } + nodes := provider.GetMasterNodes() + if len(nodes) == 0 { + return nil, errors.New("no master node found") } - return nil, errors.New("no master node found") + return &nodes[0], nil } // GetCluster returns a template for Cluster func (provider *Provider) GetCluster() clustermanager.Cluster { - return clustermanager.Cluster{ - Name: provider.clusterName, - Nodes: provider.nodes, - NodeCIDR: provider.nodeCidr, + Name: provider.clusterName, + Nodes: provider.nodes, + CloudInitFile: provider.cloudInitFile, + NodeCIDR: provider.nodeCidr, } } @@ -237,6 +215,19 @@ func (provider *Provider) Token() string { return provider.token } +type nodeFilter func(clustermanager.Node) bool + +func (provider *Provider) filterNodes(filter nodeFilter) []clustermanager.Node { + nodes := []clustermanager.Node{} + for _, node := range provider.nodes { + if filter(node) { + nodes = append(nodes, node) + } + } + + return nodes +} + func (provider *Provider) runCreateServer(opts *hcloud.ServerCreateOpts) (*hcloud.ServerCreateResult, error) { log.Printf("creating server '%s'...", opts.Name) server, _, err := provider.client.Server.GetByName(provider.context, opts.Name) diff --git a/pkg/hetzner/hetzner_provider_test.go b/pkg/hetzner/hetzner_provider_test.go index f6d7be71..1bfe6699 100644 --- a/pkg/hetzner/hetzner_provider_test.go +++ b/pkg/hetzner/hetzner_provider_test.go @@ -1,85 +1,341 @@ -package hetzner +package hetzner_test import ( "testing" "github.com/magiconair/properties/assert" "github.com/xetys/hetzner-kube/pkg/clustermanager" + "github.com/xetys/hetzner-kube/pkg/hetzner" ) -func getDefaultProviderWithNodes() ([]clustermanager.Node, Provider) { - nodes := []clustermanager.Node{ - {Name: "kube1", IPAddress: "1.1.1.1", PrivateIPAddress: "10.0.1.11", IsEtcd: true}, - {Name: "kube2", IPAddress: "1.1.1.2", PrivateIPAddress: "10.0.1.12", IsMaster: true}, - {Name: "kube3", IPAddress: "1.1.1.3", PrivateIPAddress: "10.0.1.13"}, - } - provider := Provider{nodes: nodes} - return nodes, provider +func getProviderWithNodes(nodes []clustermanager.Node) hetzner.Provider { + provider := hetzner.Provider{} + + provider.SetNodes(nodes) + + return provider } -func TestCluster_CreateEtcdNodes(t *testing.T) { - nodes, provider := getDefaultProviderWithNodes() - etcdNodes := provider.GetEtcdNodes() +type testCase struct { + Name string + Nodes []clustermanager.Node + MatchedNodes []string +} - if len(etcdNodes) != 1 { - t.Error("found more than one etcd node") - } +func getNodeNames(nodes []clustermanager.Node) []string { + nodeNames := []string{} - if etcdNodes[0].Name != nodes[0].Name { - t.Error("wrong node found") + for _, node := range nodes { + nodeNames = append(nodeNames, node.Name) } + + return nodeNames } -func TestProvider_GetMasterNodes(t *testing.T) { - nodes, provider := getDefaultProviderWithNodes() - masterNodes := provider.GetMasterNodes() +func TestProviderGetMasterNodes(t *testing.T) { + tests := []testCase{ + { + Name: "Single master node", + Nodes: []clustermanager.Node{ + {Name: "kube-master-1", IsMaster: true}, + {Name: "kube-etcd-1", IsEtcd: true}, + {Name: "kube-worker-1"}, + }, + MatchedNodes: []string{ + "kube-master-1", + }, + }, + { + Name: "Two master nodes", + Nodes: []clustermanager.Node{ + {Name: "kube-master-1", IsMaster: true}, + {Name: "kube-master-2", IsMaster: true}, + {Name: "kube-etcd-1", IsEtcd: true}, + {Name: "kube-worker-1"}, + }, + MatchedNodes: []string{ + "kube-master-1", + "kube-master-2", + }, + }, + { + Name: "Two etcd node that are also master", + Nodes: []clustermanager.Node{ + {Name: "kube-etcd-1", IsMaster: true, IsEtcd: true}, + {Name: "kube-etcd-2", IsMaster: true, IsEtcd: true}, + {Name: "kube-etcd-3", IsEtcd: true}, + {Name: "kube-worker-1"}, + }, + MatchedNodes: []string{ + "kube-etcd-1", + "kube-etcd-2", + }, + }, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + provider := getProviderWithNodes(tt.Nodes) + nodes := provider.GetMasterNodes() + + assert.Equal(t, getNodeNames(nodes), tt.MatchedNodes) + }) + } +} - if len(masterNodes) != 1 { - t.Error("found more than one maser node") +func TestProviderGetEtcdNodes(t *testing.T) { + tests := []testCase{ + { + Name: "Single etcd node", + Nodes: []clustermanager.Node{ + {Name: "kube-master-1", IsMaster: true}, + {Name: "kube-etcd-1", IsEtcd: true}, + {Name: "kube-worker-1"}, + }, + MatchedNodes: []string{ + "kube-etcd-1", + }, + }, + { + Name: "Two etcd nodes", + Nodes: []clustermanager.Node{ + {Name: "kube-master-1", IsMaster: true}, + {Name: "kube-etcd-1", IsEtcd: true}, + {Name: "kube-etcd-2", IsEtcd: true}, + {Name: "kube-worker-1"}, + }, + MatchedNodes: []string{ + "kube-etcd-1", + "kube-etcd-2", + }, + }, + { + Name: "Three etcd node some of them are also master", + Nodes: []clustermanager.Node{ + {Name: "kube-etcd-1", IsMaster: true, IsEtcd: true}, + {Name: "kube-etcd-2", IsMaster: true, IsEtcd: true}, + {Name: "kube-etcd-3", IsEtcd: true}, + {Name: "kube-worker-1"}, + }, + MatchedNodes: []string{ + "kube-etcd-1", + "kube-etcd-2", + "kube-etcd-3", + }, + }, } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + provider := getProviderWithNodes(tt.Nodes) + nodes := provider.GetEtcdNodes() - if masterNodes[0].Name != nodes[1].Name { - t.Error("wrong node found") + assert.Equal(t, getNodeNames(nodes), tt.MatchedNodes) + }) } } -func TestProvider_CreateWorkerNodes(t *testing.T) { - nodes, provider := getDefaultProviderWithNodes() - workerNodes := provider.GetWorkerNodes() +func TestProviderGetWorkerNodes(t *testing.T) { + tests := []testCase{ + { + Name: "Single worker node", + Nodes: []clustermanager.Node{ + {Name: "kube-master-1", IsMaster: true}, + {Name: "kube-etcd-1", IsEtcd: true}, + {Name: "kube-worker-1"}, + }, + MatchedNodes: []string{ + "kube-worker-1", + }, + }, + { + Name: "Two worker nodes", + Nodes: []clustermanager.Node{ + {Name: "kube-master-1", IsMaster: true}, + {Name: "kube-etcd-1", IsEtcd: true}, + {Name: "kube-worker-1"}, + {Name: "kube-worker-2"}, + }, + MatchedNodes: []string{ + "kube-worker-1", + "kube-worker-2", + }, + }, + { + Name: "No worker nodes", + Nodes: []clustermanager.Node{ + {Name: "kube-etcd-1", IsMaster: true, IsEtcd: true}, + {Name: "kube-etcd-2", IsMaster: true, IsEtcd: true}, + {Name: "kube-etcd-3", IsEtcd: true}, + }, + MatchedNodes: []string{}, + }, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + provider := getProviderWithNodes(tt.Nodes) + nodes := provider.GetWorkerNodes() - if len(workerNodes) != 1 { - t.Error("found more than one worker node") + assert.Equal(t, getNodeNames(nodes), tt.MatchedNodes) + }) } +} - if workerNodes[0].Name != nodes[2].Name { - t.Error("wrong node found") +func TestProviderGetAllNodes(t *testing.T) { + tests := []testCase{ + { + Name: "One node per type", + Nodes: []clustermanager.Node{ + {Name: "kube-master-1", IsMaster: true}, + {Name: "kube-etcd-1", IsEtcd: true}, + {Name: "kube-worker-1"}, + }, + MatchedNodes: []string{ + "kube-master-1", + "kube-etcd-1", + "kube-worker-1", + }, + }, + { + Name: "Multiple node per type", + Nodes: []clustermanager.Node{ + {Name: "kube-master-1", IsMaster: true}, + {Name: "kube-master-2", IsMaster: true}, + {Name: "kube-etcd-1", IsEtcd: true}, + {Name: "kube-etcd-2", IsEtcd: true}, + {Name: "kube-worker-1"}, + {Name: "kube-worker-2"}, + {Name: "kube-worker-3"}, + }, + MatchedNodes: []string{ + "kube-master-1", + "kube-master-2", + "kube-etcd-1", + "kube-etcd-2", + "kube-worker-1", + "kube-worker-2", + "kube-worker-3", + }, + }, + { + Name: "No nodes", + Nodes: []clustermanager.Node{}, + MatchedNodes: []string{}, + }, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + provider := getProviderWithNodes(tt.Nodes) + nodes := provider.GetAllNodes() + + assert.Equal(t, getNodeNames(nodes), tt.MatchedNodes) + }) } } -func TestProvider_GetMasterNode(t *testing.T) { - nodes, provider := getDefaultProviderWithNodes() +func TestProviderGetMasterNode(t *testing.T) { + tests := []testCase{ + { + Name: "Single master node", + Nodes: []clustermanager.Node{ + {Name: "kube-master-1", IsMaster: true}, + {Name: "kube-etcd-1", IsEtcd: true}, + {Name: "kube-worker-1"}, + }, + MatchedNodes: []string{"kube-master-1"}, + }, + { + Name: "Two master nodes", + Nodes: []clustermanager.Node{ + {Name: "kube-master-1", IsMaster: true}, + {Name: "kube-master-2", IsMaster: true}, + {Name: "kube-etcd-1", IsEtcd: true}, + {Name: "kube-worker-1"}, + }, + MatchedNodes: []string{"kube-master-1"}, + }, + { + Name: "An etcd node that is also master", + Nodes: []clustermanager.Node{ + {Name: "kube-etcd-1", IsEtcd: true}, + {Name: "kube-etcd-2", IsMaster: true, IsEtcd: true}, + {Name: "kube-etcd-3", IsEtcd: true}, + {Name: "kube-master-1", IsMaster: true}, + {Name: "kube-worker-1"}, + }, + MatchedNodes: []string{"kube-etcd-2"}, + }, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + provider := getProviderWithNodes(tt.Nodes) + node, _ := provider.GetMasterNode() - masterNode, err := provider.GetMasterNode() + assert.Equal(t, []string{node.Name}, tt.MatchedNodes) + }) + } +} - if err != nil { - t.Error(err) +func TestProviderGetMasterNodeIsMissing(t *testing.T) { + tests := []struct { + Name string + Nodes []clustermanager.Node + }{ + { + Name: "No nodes", + Nodes: []clustermanager.Node{}, + }, + { + Name: "No master nodes", + Nodes: []clustermanager.Node{ + {Name: "kube-etcd-1", IsEtcd: true}, + {Name: "kube-worker-1"}, + }, + }, } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + provider := getProviderWithNodes(tt.Nodes) + _, err := provider.GetMasterNode() - if masterNode.Name != nodes[1].Name { - t.Error("master node not found") + if err == nil { + t.Error("no error ommited with no master") + } + }) } +} - provider.SetNodes([]clustermanager.Node{}) +func TestProviderInitCluster(t *testing.T) { + provider := getProviderWithNodes([]clustermanager.Node{}) - masterNode, err = provider.GetMasterNode() + provider.InitCluster("cluster-name", "10.0.1.0/24") - if err == nil { - t.Error("no error ommited with no master") + if provider.GetNodeCidr() != "10.0.1.0/24" { + t.Error("cluster node cidr is not correctly set") } } -func TestProvider_GetAllNodes(t *testing.T) { - nodes, provider := getDefaultProviderWithNodes() - allNodes := provider.GetAllNodes() - assert.Equal(t, allNodes, nodes) +func TestProviderGetCluster(t *testing.T) { + nodes := []clustermanager.Node{ + {Name: "kube-etcd-1", IsEtcd: true}, + {Name: "kube-etcd-2", IsMaster: true, IsEtcd: true}, + {Name: "kube-etcd-3", IsEtcd: true}, + {Name: "kube-master-1", IsMaster: true}, + {Name: "kube-worker-1"}, + } + provider := getProviderWithNodes(nodes) + + provider.InitCluster("cluster-name", "10.0.1.0/24") + provider.SetCloudInitFile("cloud/init.file") + + cluster := provider.GetCluster() + expectedCluster := clustermanager.Cluster{ + Name: "cluster-name", + NodeCIDR: "10.0.1.0/24", + HaEnabled: false, + IsolatedEtcd: false, + SelfHosted: false, + CloudInitFile: "cloud/init.file", + Nodes: nodes, + } + + assert.Equal(t, cluster, expectedCluster) }