From 90b66dc0272e8aca0e4f554b1223b4ebed529dd4 Mon Sep 17 00:00:00 2001 From: Serge Logvinov Date: Sun, 30 Apr 2023 13:51:59 +0300 Subject: [PATCH] test: basic test Add basic unit tests. --- go.mod | 3 +- pkg/cluster/client.go | 22 ++++-- pkg/cluster/client_test.go | 76 +++++++++++++++++++ pkg/cluster/cloud_config.go | 26 ++++--- pkg/cluster/cloud_config_test.go | 71 +++++++++++++++++ pkg/proxmox/cloud_test.go | 80 ++++++++++++++++++++ pkg/proxmox/instances.go | 32 ++++++-- pkg/proxmox/instances_test.go | 126 +++++++++++++++++++++++++++++++ 8 files changed, 415 insertions(+), 21 deletions(-) create mode 100644 pkg/cluster/client_test.go create mode 100644 pkg/cluster/cloud_config_test.go create mode 100644 pkg/proxmox/cloud_test.go create mode 100644 pkg/proxmox/instances_test.go diff --git a/go.mod b/go.mod index 324d9d1..55bbc3d 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.20 require ( github.com/Telmate/proxmox-api-go v0.0.0-20230329163449-4d08b16c14e0 github.com/spf13/pflag v1.0.5 + github.com/stretchr/testify v1.8.2 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.27.1 k8s.io/apimachinery v0.27.1 @@ -58,13 +59,13 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/spf13/cobra v1.6.0 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect - github.com/stretchr/testify v1.8.2 // indirect go.etcd.io/etcd/api/v3 v3.5.7 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect go.etcd.io/etcd/client/v3 v3.5.7 // indirect diff --git a/pkg/cluster/client.go b/pkg/cluster/client.go index 4159144..165d9c5 100644 --- a/pkg/cluster/client.go +++ b/pkg/cluster/client.go @@ -1,3 +1,19 @@ +/* +Copyright 2023 The Kubernetes 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 cluster implements the multi-cloud provider interface for Proxmox. package cluster @@ -34,10 +50,6 @@ func NewClient(config *ClustersConfig) (*Client, error) { client.SetAPIToken(cfg.TokenID, cfg.TokenSecret) - if _, err := client.GetVersion(); err != nil { - return nil, fmt.Errorf("failed to initialized proxmox client in cluster %s: %v", cfg.Region, err) - } - proxmox[cfg.Region] = client } @@ -47,7 +59,7 @@ func NewClient(config *ClustersConfig) (*Client, error) { }, nil } - return nil, nil + return nil, fmt.Errorf("no Proxmox clusters found") } // CheckClusters checks if the Proxmox connection is working. diff --git a/pkg/cluster/client_test.go b/pkg/cluster/client_test.go new file mode 100644 index 0000000..cb7ece2 --- /dev/null +++ b/pkg/cluster/client_test.go @@ -0,0 +1,76 @@ +/* +Copyright 2023 The Kubernetes 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 cluster + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func newClientEnv() (*ClustersConfig, error) { + cfg, err := ReadCloudConfig(strings.NewReader(` +clusters: + - url: https://127.0.0.1:8006 + insecure: false + token_id: "user!token-id" + token_secret: "secret" + region: cluster-1 +`)) + + return &cfg, err +} + +func TestNewClient(t *testing.T) { + cfg, err := newClientEnv() + assert.Nil(t, err) + assert.NotNil(t, cfg) + + client, err := NewClient(&ClustersConfig{}) + assert.NotNil(t, err) + assert.Nil(t, client) + + client, err = NewClient(cfg) + assert.Nil(t, err) + assert.NotNil(t, client) + assert.Equal(t, 1, len(client.proxmox)) +} + +func TestCheckClusters(t *testing.T) { + cfg, err := newClientEnv() + assert.Nil(t, err) + assert.NotNil(t, cfg) + + client, err := NewClient(cfg) + assert.Nil(t, err) + assert.NotNil(t, client) + assert.Equal(t, 1, len(client.proxmox)) + + pxapi, err := client.GetProxmoxCluster("test") + assert.NotNil(t, err) + assert.Nil(t, pxapi) + assert.Equal(t, "proxmox cluster test not found", err.Error()) + + pxapi, err = client.GetProxmoxCluster("cluster-1") + assert.Nil(t, err) + assert.NotNil(t, pxapi) + + err = client.CheckClusters() + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "failed to initialized proxmox client in region") +} diff --git a/pkg/cluster/cloud_config.go b/pkg/cluster/cloud_config.go index 5322a48..d7d4bf5 100644 --- a/pkg/cluster/cloud_config.go +++ b/pkg/cluster/cloud_config.go @@ -1,3 +1,19 @@ +/* +Copyright 2023 The Kubernetes 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 cluster import ( @@ -41,13 +57,5 @@ func ReadCloudConfigFromFile(file string) (ClustersConfig, error) { } defer f.Close() // nolint: errcheck - cfg := ClustersConfig{} - - if f != nil { - if err := yaml.NewDecoder(f).Decode(&cfg); err != nil { - return ClustersConfig{}, err - } - } - - return cfg, nil + return ReadCloudConfig(f) } diff --git a/pkg/cluster/cloud_config_test.go b/pkg/cluster/cloud_config_test.go new file mode 100644 index 0000000..1e9680f --- /dev/null +++ b/pkg/cluster/cloud_config_test.go @@ -0,0 +1,71 @@ +/* +Copyright 2023 The Kubernetes 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 cluster + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestReadCloudConfig(t *testing.T) { + cfg, err := ReadCloudConfig(nil) + assert.Nil(t, err) + assert.NotNil(t, cfg) + + // Empty config + cfg, err = ReadCloudConfig(strings.NewReader(` +clusters: +`)) + assert.Nil(t, err) + assert.NotNil(t, cfg) + + // Wrong config + cfg, err = ReadCloudConfig(strings.NewReader(` +clusters: + test: false +`)) + + assert.NotNil(t, err) + assert.NotNil(t, cfg) + + // Valid config with one cluster + cfg, err = ReadCloudConfig(strings.NewReader(` +clusters: + - url: https://example.com + insecure: false + token_id: "user!token-id" + token_secret: "secret" + region: cluster-1 +`)) + assert.Nil(t, err) + assert.NotNil(t, cfg) + assert.Equal(t, 1, len(cfg.Clusters)) +} + +func TestReadCloudConfigFromFile(t *testing.T) { + cfg, err := ReadCloudConfigFromFile("testdata/cloud-config.yaml") + assert.NotNil(t, err) + assert.EqualError(t, err, "error reading testdata/cloud-config.yaml: open testdata/cloud-config.yaml: no such file or directory") + assert.NotNil(t, cfg) + + cfg, err = ReadCloudConfigFromFile("../../hack/proxmox-config.yaml") + assert.Nil(t, err) + assert.NotNil(t, cfg) + assert.Equal(t, 2, len(cfg.Clusters)) +} diff --git a/pkg/proxmox/cloud_test.go b/pkg/proxmox/cloud_test.go new file mode 100644 index 0000000..7c47e64 --- /dev/null +++ b/pkg/proxmox/cloud_test.go @@ -0,0 +1,80 @@ +/* +Copyright 2023 The Kubernetes 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 proxmox + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/cluster" +) + +func TestNewCloudError(t *testing.T) { + cloud, err := newCloud(&cluster.ClustersConfig{}) + assert.NotNil(t, err) + assert.Nil(t, cloud) + assert.EqualError(t, err, "no Proxmox clusters found") +} + +func TestCloud(t *testing.T) { + cfg, err := cluster.ReadCloudConfig(strings.NewReader(` +clusters: + - url: https://example.com + insecure: false + token_id: "user!token-id" + token_secret: "secret" + region: cluster-1 +`)) + assert.Nil(t, err) + assert.NotNil(t, cfg) + + cloud, err := newCloud(&cfg) + assert.Nil(t, err) + assert.NotNil(t, cloud) + + lb, res := cloud.LoadBalancer() + assert.Nil(t, lb) + assert.Equal(t, res, false) + + ins, res := cloud.Instances() + assert.Nil(t, ins) + assert.Equal(t, res, false) + + ins2, res := cloud.InstancesV2() + assert.NotNil(t, ins2) + assert.Equal(t, res, true) + + zone, res := cloud.Zones() + assert.Nil(t, zone) + assert.Equal(t, res, false) + + cl, res := cloud.Clusters() + assert.Nil(t, cl) + assert.Equal(t, res, false) + + route, res := cloud.Routes() + assert.Nil(t, route) + assert.Equal(t, res, false) + + pName := cloud.ProviderName() + assert.Equal(t, pName, ProviderName) + + clID := cloud.HasClusterID() + assert.Equal(t, clID, true) +} diff --git a/pkg/proxmox/instances.go b/pkg/proxmox/instances.go index 8d22913..5d68f40 100644 --- a/pkg/proxmox/instances.go +++ b/pkg/proxmox/instances.go @@ -1,3 +1,19 @@ +/* +Copyright 2023 The Kubernetes 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 proxmox import ( @@ -141,10 +157,6 @@ func (i *instances) InstanceMetadata(_ context.Context, node *v1.Node) (*cloudpr return &cloudprovider.InstanceMetadata{}, nil } -func (i *instances) getProviderID(region string, vmr *pxapi.VmRef) string { - return fmt.Sprintf("%s://%s/%d", ProviderName, region, vmr.VmId()) -} - func (i *instances) getInstance(node *v1.Node) (*pxapi.VmRef, string, error) { if !strings.HasPrefix(node.Spec.ProviderID, ProviderName) { klog.V(4).Infof("instances.getInstance() node %s has foreign providerID: %s, skipped", node.Name, node.Spec.ProviderID) @@ -194,15 +206,23 @@ func (i *instances) getInstanceType(vmRef *pxapi.VmRef, region string) (string, var providerIDRegexp = regexp.MustCompile(`^` + ProviderName + `://([^/]*)/([^/]+)$`) +func (i *instances) getProviderID(region string, vmr *pxapi.VmRef) string { + return fmt.Sprintf("%s://%s/%d", ProviderName, region, vmr.VmId()) +} + func (i *instances) parseProviderID(providerID string) (*pxapi.VmRef, string, error) { + if !strings.HasPrefix(providerID, ProviderName) { + return nil, "", fmt.Errorf("foreign providerID or empty \"%s\"", providerID) + } + matches := providerIDRegexp.FindStringSubmatch(providerID) if len(matches) != 3 { - return nil, "", fmt.Errorf("ProviderID \"%s\" didn't match expected format \"%s://region/InstanceID\"", providerID, ProviderName) + return nil, "", fmt.Errorf("providerID \"%s\" didn't match expected format \"%s://region/InstanceID\"", providerID, ProviderName) } vmID, err := strconv.Atoi(matches[2]) if err != nil { - return nil, "", err + return nil, "", fmt.Errorf("providerID \"%s\" didn't match expected format \"%s://region/InstanceID\"", providerID, ProviderName) } return pxapi.NewVmRef(vmID), matches[1], nil diff --git a/pkg/proxmox/instances_test.go b/pkg/proxmox/instances_test.go new file mode 100644 index 0000000..83913e5 --- /dev/null +++ b/pkg/proxmox/instances_test.go @@ -0,0 +1,126 @@ +/* +Copyright 2023 The Kubernetes 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 proxmox + +import ( + "fmt" + "testing" + + pxapi "github.com/Telmate/proxmox-api-go/proxmox" + "github.com/stretchr/testify/assert" +) + +func TestGetProviderID(t *testing.T) { + t.Parallel() + + i := newInstances(nil) + + tests := []struct { + msg string + region string + vmr *pxapi.VmRef + expected string + }{ + { + msg: "empty region", + region: "", + vmr: pxapi.NewVmRef(100), + expected: "proxmox:///100", + }, + { + msg: "region", + region: "cluster1", + vmr: pxapi.NewVmRef(100), + expected: "proxmox://cluster1/100", + }, + } + + for _, testCase := range tests { + testCase := testCase + + t.Run(fmt.Sprint(testCase.msg), func(t *testing.T) { + t.Parallel() + + expected := i.getProviderID(testCase.region, testCase.vmr) + assert.Equal(t, expected, testCase.expected) + }) + } +} + +func TestParseProviderID(t *testing.T) { + t.Parallel() + + i := newInstances(nil) + + tests := []struct { + msg string + magic string + expectedCluster string + expectedVmr *pxapi.VmRef + expectedError error + }{ + { + msg: "Empty magic string", + magic: "", + expectedError: fmt.Errorf("foreign providerID or empty \"\""), + }, + { + msg: "Wrong provider", + magic: "provider://region/100", + expectedError: fmt.Errorf("foreign providerID or empty \"provider://region/100\""), + }, + { + msg: "Empty region", + magic: "proxmox:///100", + expectedCluster: "", + expectedVmr: pxapi.NewVmRef(100), + }, + { + msg: "Empty region", + magic: "proxmox://100", + expectedError: fmt.Errorf("providerID \"proxmox://100\" didn't match expected format \"proxmox://region/InstanceID\""), + }, + { + msg: "Cluster and InstanceID", + magic: "proxmox://cluster/100", + expectedCluster: "cluster", + expectedVmr: pxapi.NewVmRef(100), + }, + { + msg: "Cluster and wrong InstanceID", + magic: "proxmox://cluster/name", + expectedError: fmt.Errorf("providerID \"proxmox://cluster/name\" didn't match expected format \"proxmox://region/InstanceID\""), + }, + } + + for _, testCase := range tests { + testCase := testCase + + t.Run(fmt.Sprint(testCase.msg), func(t *testing.T) { + t.Parallel() + + vmr, cluster, err := i.parseProviderID(testCase.magic) + + if testCase.expectedError != nil { + assert.Equal(t, testCase.expectedError, err) + } else { + assert.Equal(t, testCase.expectedVmr, vmr) + assert.Equal(t, testCase.expectedCluster, cluster) + } + }) + } +}