From 253220cd62c51acd39277ac122f4bb517f60b198 Mon Sep 17 00:00:00 2001 From: Hans Rakers Date: Mon, 2 Sep 2024 21:25:17 +0200 Subject: [PATCH 1/3] fix(tests): update webhook tests to admission v1 --- api/v1beta3/webhook_suite_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/v1beta3/webhook_suite_test.go b/api/v1beta3/webhook_suite_test.go index eef6538f..0ca9bb49 100644 --- a/api/v1beta3/webhook_suite_test.go +++ b/api/v1beta3/webhook_suite_test.go @@ -30,7 +30,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - admissionv1beta1 "k8s.io/api/admission/v1beta1" + admissionv1 "k8s.io/api/admission/v1" //+kubebuilder:scaffold:imports "k8s.io/apimachinery/pkg/runtime" infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" @@ -81,7 +81,7 @@ var _ = BeforeSuite(func() { err = infrav1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) - err = admissionv1beta1.AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme From 7de68d93ae75482c3af4ffa47fbe404257d59a54 Mon Sep 17 00:00:00 2001 From: Hans Rakers Date: Mon, 2 Sep 2024 21:27:29 +0200 Subject: [PATCH 2/3] chore: add missing layout to kubebuilder PROJECT file --- PROJECT | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PROJECT b/PROJECT index ea4068e3..823b1ef0 100644 --- a/PROJECT +++ b/PROJECT @@ -1,4 +1,6 @@ domain: cluster.x-k8s.io +layout: +- go.kubebuilder.io/v3 projectName: cluster-api-provider-capc repo: sigs.k8s.io/cluster-api-provider-cloudstack resources: From b514c8a2edd6853be29c997ac58bb2e5c3c79ec5 Mon Sep 17 00:00:00 2001 From: Hans Rakers Date: Wed, 4 Sep 2024 13:25:10 +0200 Subject: [PATCH 3/3] chore: lint fixes --- .golangci.yml | 87 +++++++-- .../cloudstackaffinitygroup_conversion.go | 20 ++- api/v1beta1/cloudstackaffinitygroup_types.go | 8 +- api/v1beta1/cloudstackcluster_conversion.go | 13 +- api/v1beta1/cloudstackcluster_types.go | 10 +- .../cloudstackisolatednetwork_conversion.go | 35 ++-- .../cloudstackisolatednetwork_types.go | 15 +- api/v1beta1/cloudstackmachine_conversion.go | 22 +-- api/v1beta1/cloudstackmachine_types.go | 7 +- api/v1beta1/cloudstackmachine_types_test.go | 6 +- ...loudstackmachinestatechecker_conversion.go | 13 +- .../cloudstackmachinestatechecker_types.go | 10 +- .../cloudstackmachinetemplate_conversion.go | 29 +-- .../cloudstackmachinetemplate_types.go | 6 +- api/v1beta1/cloudstackzone_types.go | 8 +- api/v1beta1/conversion.go | 23 ++- api/v1beta1/conversion_test.go | 3 +- api/v1beta1/doc.go | 2 +- api/v1beta1/groupversion_info.go | 8 +- .../cloudstackaffinitygroup_conversion.go | 13 +- api/v1beta2/cloudstackaffinitygroup_types.go | 8 +- api/v1beta2/cloudstackcluster_conversion.go | 17 +- api/v1beta2/cloudstackcluster_types.go | 4 +- .../cloudstackfailuredomain_conversion.go | 13 +- api/v1beta2/cloudstackfailuredomain_types.go | 8 +- .../cloudstackisolatednetwork_conversion.go | 15 +- .../cloudstackisolatednetwork_types.go | 15 +- api/v1beta2/cloudstackmachine_conversion.go | 26 +-- api/v1beta2/cloudstackmachine_types.go | 11 +- api/v1beta2/cloudstackmachine_types_test.go | 14 +- ...loudstackmachinestatechecker_conversion.go | 13 +- .../cloudstackmachinestatechecker_types.go | 10 +- .../cloudstackmachinetemplate_conversion.go | 26 +-- .../cloudstackmachinetemplate_types.go | 6 +- api/v1beta2/conversion.go | 1 + api/v1beta2/doc.go | 2 +- api/v1beta2/groupversion_info.go | 6 +- api/v1beta3/cloudstackaffinitygroup_types.go | 8 +- api/v1beta3/cloudstackcluster_types.go | 4 +- api/v1beta3/cloudstackcluster_webhook.go | 19 +- api/v1beta3/cloudstackcluster_webhook_test.go | 18 +- api/v1beta3/cloudstackfailuredomain_types.go | 8 +- .../cloudstackisolatednetwork_types.go | 11 +- api/v1beta3/cloudstackmachine_types.go | 33 ++-- api/v1beta3/cloudstackmachine_webhook.go | 15 +- api/v1beta3/cloudstackmachine_webhook_test.go | 6 +- .../cloudstackmachinestatechecker_types.go | 8 +- .../cloudstackmachinetemplate_types.go | 8 +- .../cloudstackmachinetemplate_webhook.go | 17 +- .../cloudstackmachinetemplate_webhook_test.go | 9 +- api/v1beta3/doc.go | 2 +- api/v1beta3/groupversion_info.go | 6 +- api/v1beta3/webhook_suite_test.go | 64 ++++--- .../cloudstackaffinitygroup_controller.go | 5 + ...cloudstackaffinitygroup_controller_test.go | 8 +- controllers/cloudstackcluster_controller.go | 17 +- .../cloudstackcluster_controller_test.go | 6 +- .../cloudstackfailuredomain_controller.go | 99 ++++++----- ...cloudstackfailuredomain_controller_test.go | 21 ++- .../cloudstackisolatednetwork_controller.go | 25 +-- ...oudstackisolatednetwork_controller_test.go | 36 +++- controllers/cloudstackmachine_controller.go | 61 ++++--- .../cloudstackmachine_controller_test.go | 27 +-- ...loudstackmachinestatechecker_controller.go | 10 +- controllers/controllers_suite_test.go | 83 ++++----- controllers/utils/affinity_group.go | 24 +-- controllers/utils/base_reconciler.go | 63 +++++-- controllers/utils/constants.go | 6 +- controllers/utils/failuredomains.go | 19 +- controllers/utils/isolated_network.go | 5 +- controllers/utils/utils.go | 33 +++- main.go | 15 +- pkg/cloud/affinity_groups.go | 49 ++++-- pkg/cloud/affinity_groups_test.go | 2 +- pkg/cloud/client.go | 49 ++++-- pkg/cloud/client_test.go | 16 +- pkg/cloud/cloud_suite_test.go | 4 +- pkg/cloud/helpers.go | 9 +- pkg/cloud/helpers_test.go | 5 +- pkg/cloud/instance.go | 166 +++++++++++------- pkg/cloud/instance_test.go | 73 ++++---- pkg/cloud/isolated_network.go | 51 ++++-- pkg/cloud/isolated_network_test.go | 56 ++++-- pkg/cloud/network.go | 11 +- pkg/cloud/network_test.go | 1 + pkg/cloud/tags.go | 52 +++--- pkg/cloud/tags_test.go | 9 +- pkg/cloud/user_credentials.go | 33 ++-- pkg/cloud/user_credentials_test.go | 6 +- pkg/cloud/zone.go | 9 +- pkg/cloud/zone_test.go | 7 +- pkg/metrics/metrics.go | 13 +- pkg/utils/strings/strings.go | 2 + pkg/utils/strings/strings_test.go | 8 +- pkg/webhookutil/webhook_validators.go | 21 ++- 95 files changed, 1219 insertions(+), 765 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 1e747287..61de1e35 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,4 +1,35 @@ linters-settings: + cyclop: + max-complexity: 20 + gci: + sections: + - standard + - default + - prefix(sigs.k8s.io/cluster-api-provider-cloudstack) + gocritic: + enabled-tags: + - diagnostic + - experimental + - performance + disabled-checks: + - appendAssign + - dupImport # https://github.com/go-critic/go-critic/issues/845 + - emptyStringTest + - evalOrder + - ifElseChain + - octalLiteral + - regexpSimplify + - sloppyReassign + - truncateCmp + - typeDefFirst + - unnamedResult + - unnecessaryDefer + - whyNoLint + - wrapperFunc + - rangeValCopy + - hugeParam + gocyclo: + min-complexity: 15 goheader: values: const: @@ -19,8 +50,11 @@ linters-settings: 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. - gocyclo: - min-complexity: 15 + goimports: + local-prefixes: sigs.k8s.io/cluster-api-provider-cloudstack + nestif: + # minimal complexity of if statements to report, 5 by default + min-complexity: 8 revive: rules: - name: dot-imports @@ -31,18 +65,36 @@ linters-settings: - "github.com/onsi/gomega" linters: - enable: - - gocyclo - - goheader - - gosec - - misspell - - revive - - staticcheck + disable: + - containedctx + - depguard + - err113 + - execinquery + - exhaustruct + - forcetypeassert + - funlen + - gochecknoglobals + - gochecknoinits + - godox + - gomnd + - interfacebloat + - ireturn + - lll + - mnd + - nonamedreturns + - paralleltest + - tagliatelle + - varnamelen + - wrapcheck + - wsl + enable-all: true run: issues-exit-code: 1 issues: + max-same-issues: 0 + max-issues-per-linter: 0 # Excluding configuration per-path, per-linter, per-text and per-source exclude-dirs: - pkg/mocks @@ -51,7 +103,22 @@ issues: # Exclude some linters from running on tests files. - path: _test\.go linters: + - dupl - gosec - text: "SA1019: .+LBRuleID is deprecated" linters: - - staticcheck \ No newline at end of file + - staticcheck + - path: api/v1beta3/webhook_suite_test.go + linters: + - goimports + - linters: + - revive + - stylecheck + path: (test)/.*.go + text: should not use dot imports + - linters: + - stylecheck + text: "ST1003: should not use underscores in Go names;" + path: api\/.*\/.*conversion.*\.go$ + exclude-files: + - "zz_generated.*\\.go$" \ No newline at end of file diff --git a/api/v1beta1/cloudstackaffinitygroup_conversion.go b/api/v1beta1/cloudstackaffinitygroup_conversion.go index 685d1ad9..367e431e 100644 --- a/api/v1beta1/cloudstackaffinitygroup_conversion.go +++ b/api/v1beta1/cloudstackaffinitygroup_conversion.go @@ -18,38 +18,40 @@ package v1beta1 import ( machineryconversion "k8s.io/apimachinery/pkg/conversion" - "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" utilconversion "sigs.k8s.io/cluster-api/util/conversion" "sigs.k8s.io/controller-runtime/pkg/conversion" + + "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) -func (src *CloudStackAffinityGroup) ConvertTo(dstRaw conversion.Hub) error { // nolint +func (r *CloudStackAffinityGroup) ConvertTo(dstRaw conversion.Hub) error { dst := dstRaw.(*v1beta3.CloudStackAffinityGroup) - if err := Convert_v1beta1_CloudStackAffinityGroup_To_v1beta3_CloudStackAffinityGroup(src, dst, nil); err != nil { + if err := Convert_v1beta1_CloudStackAffinityGroup_To_v1beta3_CloudStackAffinityGroup(r, dst, nil); err != nil { return err } // Manually restore data restored := &v1beta3.CloudStackAffinityGroup{} - if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { + if ok, err := utilconversion.UnmarshalData(r, restored); err != nil || !ok { return err } if restored.Spec.FailureDomainName != "" { dst.Spec.FailureDomainName = restored.Spec.FailureDomainName } + return nil } -func (dst *CloudStackAffinityGroup) ConvertFrom(srcRaw conversion.Hub) error { // nolint +func (r *CloudStackAffinityGroup) ConvertFrom(srcRaw conversion.Hub) error { src := srcRaw.(*v1beta3.CloudStackAffinityGroup) - if err := Convert_v1beta3_CloudStackAffinityGroup_To_v1beta1_CloudStackAffinityGroup(src, dst, nil); err != nil { + if err := Convert_v1beta3_CloudStackAffinityGroup_To_v1beta1_CloudStackAffinityGroup(src, r, nil); err != nil { return err } - // Preserve Hub data on down-conversion - return utilconversion.MarshalData(src, dst) + // Preserve Hub data on down-conversion. + return utilconversion.MarshalData(src, r) } -func Convert_v1beta3_CloudStackAffinityGroupSpec_To_v1beta1_CloudStackAffinityGroupSpec(in *v1beta3.CloudStackAffinityGroupSpec, out *CloudStackAffinityGroupSpec, s machineryconversion.Scope) error { // nolint +func Convert_v1beta3_CloudStackAffinityGroupSpec_To_v1beta1_CloudStackAffinityGroupSpec(in *v1beta3.CloudStackAffinityGroupSpec, out *CloudStackAffinityGroupSpec, s machineryconversion.Scope) error { return autoConvert_v1beta3_CloudStackAffinityGroupSpec_To_v1beta1_CloudStackAffinityGroupSpec(in, out, s) } diff --git a/api/v1beta1/cloudstackaffinitygroup_types.go b/api/v1beta1/cloudstackaffinitygroup_types.go index 5d0f00a0..1c3de7c5 100644 --- a/api/v1beta1/cloudstackaffinitygroup_types.go +++ b/api/v1beta1/cloudstackaffinitygroup_types.go @@ -24,7 +24,7 @@ const ( AffinityGroupFinalizer = "affinitygroup.infrastructure.cluster.x-k8s.io" ) -// CloudStackAffinityGroupSpec defines the desired state of CloudStackAffinityGroup +// CloudStackAffinityGroupSpec defines the desired state of CloudStackAffinityGroup. type CloudStackAffinityGroupSpec struct { // Mutually exclusive parameter with AffinityGroupIDs. // Can be "host affinity", "host anti-affinity", "non-strict host affinity"or "non-strict host anti-affinity". Will create an affinity group per machine set. @@ -38,7 +38,7 @@ type CloudStackAffinityGroupSpec struct { ID string `json:"id,omitempty"` } -// CloudStackAffinityGroupStatus defines the observed state of CloudStackAffinityGroup +// CloudStackAffinityGroupStatus defines the observed state of CloudStackAffinityGroup. type CloudStackAffinityGroupStatus struct { // Reflects the readiness of the CS Affinity Group. Ready bool `json:"ready"` @@ -47,7 +47,7 @@ type CloudStackAffinityGroupStatus struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -// CloudStackAffinityGroup is the Schema for the cloudstackaffinitygroups API +// CloudStackAffinityGroup is the Schema for the cloudstackaffinitygroups API. type CloudStackAffinityGroup struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -58,7 +58,7 @@ type CloudStackAffinityGroup struct { //+kubebuilder:object:root=true -// CloudStackAffinityGroupList contains a list of CloudStackAffinityGroup +// CloudStackAffinityGroupList contains a list of CloudStackAffinityGroup. type CloudStackAffinityGroupList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta1/cloudstackcluster_conversion.go b/api/v1beta1/cloudstackcluster_conversion.go index 2e383000..689ca5bf 100644 --- a/api/v1beta1/cloudstackcluster_conversion.go +++ b/api/v1beta1/cloudstackcluster_conversion.go @@ -17,16 +17,19 @@ limitations under the License. package v1beta1 import ( - "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" "sigs.k8s.io/controller-runtime/pkg/conversion" + + "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) -func (src *CloudStackCluster) ConvertTo(dstRaw conversion.Hub) error { // nolint +func (r *CloudStackCluster) ConvertTo(dstRaw conversion.Hub) error { dst := dstRaw.(*v1beta3.CloudStackCluster) - return Convert_v1beta1_CloudStackCluster_To_v1beta3_CloudStackCluster(src, dst, nil) + + return Convert_v1beta1_CloudStackCluster_To_v1beta3_CloudStackCluster(r, dst, nil) } -func (dst *CloudStackCluster) ConvertFrom(srcRaw conversion.Hub) error { // nolint +func (r *CloudStackCluster) ConvertFrom(srcRaw conversion.Hub) error { src := srcRaw.(*v1beta3.CloudStackCluster) - return Convert_v1beta3_CloudStackCluster_To_v1beta1_CloudStackCluster(src, dst, nil) + + return Convert_v1beta3_CloudStackCluster_To_v1beta1_CloudStackCluster(src, r, nil) } diff --git a/api/v1beta1/cloudstackcluster_types.go b/api/v1beta1/cloudstackcluster_types.go index 9fe31fc9..eaff2076 100644 --- a/api/v1beta1/cloudstackcluster_types.go +++ b/api/v1beta1/cloudstackcluster_types.go @@ -30,10 +30,10 @@ const ( NetworkTypeShared = "Shared" ) -// CloudStackIdentityReference is a reference to an infrastructure +// CloudStackIdentityReference is a reference to an infrastructure. // provider identity to be used to provision cluster resources. type CloudStackIdentityReference struct { - // Kind of the identity. Must be supported by the infrastructure provider + // Kind of the identity. Must be supported by the infrastructure provider, // and may be either cluster or namespace-scoped. // +kubebuilder:validation:MinLength=1 Kind string `json:"kind"` @@ -78,6 +78,7 @@ func (z *Zone) MetaName() string { if s == "" { s = z.ID } + return strings.ToLower(s) } @@ -104,7 +105,6 @@ type CloudStackClusterSpec struct { // +k8s:conversion-gen=false // The status of the abstract CS k8s (not an actual Cloudstack Cluster) cluster. type CloudStackClusterStatus struct { - // The status of the cluster's ACS Zones. // +optional Zones ZoneStatusMap `json:"zones,omitempty"` @@ -133,7 +133,7 @@ type CloudStackClusterStatus struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +k8s:conversion-gen=false -// CloudStackCluster is the Schema for the cloudstackclusters API +// CloudStackCluster is the Schema for the cloudstackclusters API. type CloudStackCluster struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -146,7 +146,7 @@ type CloudStackCluster struct { // +kubebuilder:object:root=true // +k8s:conversion-gen=false -// CloudStackClusterList contains a list of CloudStackCluster +// CloudStackClusterList contains a list of CloudStackCluster. type CloudStackClusterList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta1/cloudstackisolatednetwork_conversion.go b/api/v1beta1/cloudstackisolatednetwork_conversion.go index 37a64b49..4cacbf9b 100644 --- a/api/v1beta1/cloudstackisolatednetwork_conversion.go +++ b/api/v1beta1/cloudstackisolatednetwork_conversion.go @@ -18,55 +18,58 @@ package v1beta1 import ( machineryconversion "k8s.io/apimachinery/pkg/conversion" - "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" - infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" utilconversion "sigs.k8s.io/cluster-api/util/conversion" "sigs.k8s.io/controller-runtime/pkg/conversion" + + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) -func (src *CloudStackIsolatedNetwork) ConvertTo(dstRaw conversion.Hub) error { // nolint - dst := dstRaw.(*v1beta3.CloudStackIsolatedNetwork) - if err := Convert_v1beta1_CloudStackIsolatedNetwork_To_v1beta3_CloudStackIsolatedNetwork(src, dst, nil); err != nil { +func (r *CloudStackIsolatedNetwork) ConvertTo(dstRaw conversion.Hub) error { + dst := dstRaw.(*infrav1.CloudStackIsolatedNetwork) + if err := Convert_v1beta1_CloudStackIsolatedNetwork_To_v1beta3_CloudStackIsolatedNetwork(r, dst, nil); err != nil { return err } - // Manually restore data - restored := &v1beta3.CloudStackIsolatedNetwork{} - if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { + // Manually restore data. + restored := &infrav1.CloudStackIsolatedNetwork{} + if ok, err := utilconversion.UnmarshalData(r, restored); err != nil || !ok { return err } if restored.Spec.FailureDomainName != "" { dst.Spec.FailureDomainName = restored.Spec.FailureDomainName } + return nil } -func (dst *CloudStackIsolatedNetwork) ConvertFrom(srcRaw conversion.Hub) error { // nolint - src := srcRaw.(*v1beta3.CloudStackIsolatedNetwork) - if err := Convert_v1beta3_CloudStackIsolatedNetwork_To_v1beta1_CloudStackIsolatedNetwork(src, dst, nil); err != nil { +func (r *CloudStackIsolatedNetwork) ConvertFrom(srcRaw conversion.Hub) error { + src := srcRaw.(*infrav1.CloudStackIsolatedNetwork) + if err := Convert_v1beta3_CloudStackIsolatedNetwork_To_v1beta1_CloudStackIsolatedNetwork(src, r, nil); err != nil { return err } - // Preserve Hub data on down-conversion - return utilconversion.MarshalData(src, dst) + // Preserve Hub data on down-conversion. + return utilconversion.MarshalData(src, r) } -func Convert_v1beta3_CloudStackIsolatedNetworkSpec_To_v1beta1_CloudStackIsolatedNetworkSpec(in *v1beta3.CloudStackIsolatedNetworkSpec, out *CloudStackIsolatedNetworkSpec, s machineryconversion.Scope) error { // nolint +func Convert_v1beta3_CloudStackIsolatedNetworkSpec_To_v1beta1_CloudStackIsolatedNetworkSpec(in *infrav1.CloudStackIsolatedNetworkSpec, out *CloudStackIsolatedNetworkSpec, s machineryconversion.Scope) error { return autoConvert_v1beta3_CloudStackIsolatedNetworkSpec_To_v1beta1_CloudStackIsolatedNetworkSpec(in, out, s) } -func Convert_v1beta1_CloudStackIsolatedNetworkStatus_To_v1beta3_CloudStackIsolatedNetworkStatus(in *CloudStackIsolatedNetworkStatus, out *v1beta3.CloudStackIsolatedNetworkStatus, s machineryconversion.Scope) error { +func Convert_v1beta1_CloudStackIsolatedNetworkStatus_To_v1beta3_CloudStackIsolatedNetworkStatus(in *CloudStackIsolatedNetworkStatus, out *infrav1.CloudStackIsolatedNetworkStatus, s machineryconversion.Scope) error { out.PublicIPID = in.PublicIPID out.LBRuleID = in.LBRuleID out.APIServerLoadBalancer = &infrav1.LoadBalancer{} out.LoadBalancerRuleIDs = []string{in.LBRuleID} out.Ready = in.Ready + return nil } -func Convert_v1beta3_CloudStackIsolatedNetworkStatus_To_v1beta1_CloudStackIsolatedNetworkStatus(in *v1beta3.CloudStackIsolatedNetworkStatus, out *CloudStackIsolatedNetworkStatus, s machineryconversion.Scope) error { +func Convert_v1beta3_CloudStackIsolatedNetworkStatus_To_v1beta1_CloudStackIsolatedNetworkStatus(in *infrav1.CloudStackIsolatedNetworkStatus, out *CloudStackIsolatedNetworkStatus, s machineryconversion.Scope) error { out.PublicIPID = in.PublicIPID out.LBRuleID = in.LBRuleID out.Ready = in.Ready + return nil } diff --git a/api/v1beta1/cloudstackisolatednetwork_types.go b/api/v1beta1/cloudstackisolatednetwork_types.go index 8033322a..21c30dbf 100644 --- a/api/v1beta1/cloudstackisolatednetwork_types.go +++ b/api/v1beta1/cloudstackisolatednetwork_types.go @@ -26,7 +26,7 @@ const ( IsolatedNetworkFinalizer = "cloudstackisolatednetwork.infrastructure.cluster.x-k8s.io" ) -// CloudStackIsolatedNetworkSpec defines the desired state of CloudStackIsolatedNetwork +// CloudStackIsolatedNetworkSpec defines the desired state of CloudStackIsolatedNetwork. type CloudStackIsolatedNetworkSpec struct { // Name. //+optional @@ -40,7 +40,7 @@ type CloudStackIsolatedNetworkSpec struct { ControlPlaneEndpoint clusterv1.APIEndpoint `json:"controlPlaneEndpoint"` } -// CloudStackIsolatedNetworkStatus defines the observed state of CloudStackIsolatedNetwork +// CloudStackIsolatedNetworkStatus defines the observed state of CloudStackIsolatedNetwork. type CloudStackIsolatedNetworkStatus struct { // The CS public IP ID to use for the k8s endpoint. PublicIPID string `json:"publicIPID,omitempty"` @@ -52,17 +52,18 @@ type CloudStackIsolatedNetworkStatus struct { Ready bool `json:"ready"` } -func (n *CloudStackIsolatedNetwork) Network() *Network { +func (r *CloudStackIsolatedNetwork) Network() *Network { return &Network{ - Name: n.Spec.Name, + Name: r.Spec.Name, Type: "IsolatedNetwork", - ID: n.Spec.ID} + ID: r.Spec.ID, + } } //+kubebuilder:object:root=true //+kubebuilder:subresource:status -// CloudStackIsolatedNetwork is the Schema for the cloudstackisolatednetworks API +// CloudStackIsolatedNetwork is the Schema for the cloudstackisolatednetworks API. type CloudStackIsolatedNetwork struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -73,7 +74,7 @@ type CloudStackIsolatedNetwork struct { //+kubebuilder:object:root=true -// CloudStackIsolatedNetworkList contains a list of CloudStackIsolatedNetwork +// CloudStackIsolatedNetworkList contains a list of CloudStackIsolatedNetwork. type CloudStackIsolatedNetworkList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta1/cloudstackmachine_conversion.go b/api/v1beta1/cloudstackmachine_conversion.go index 55de6480..25ff1061 100644 --- a/api/v1beta1/cloudstackmachine_conversion.go +++ b/api/v1beta1/cloudstackmachine_conversion.go @@ -18,21 +18,22 @@ package v1beta1 import ( machineryconversion "k8s.io/apimachinery/pkg/conversion" - "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" utilconversion "sigs.k8s.io/cluster-api/util/conversion" "sigs.k8s.io/controller-runtime/pkg/conversion" + + "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) -func (src *CloudStackMachine) ConvertTo(dstRaw conversion.Hub) error { // nolint +func (r *CloudStackMachine) ConvertTo(dstRaw conversion.Hub) error { dst := dstRaw.(*v1beta3.CloudStackMachine) - if err := Convert_v1beta1_CloudStackMachine_To_v1beta3_CloudStackMachine(src, dst, nil); err != nil { + if err := Convert_v1beta1_CloudStackMachine_To_v1beta3_CloudStackMachine(r, dst, nil); err != nil { return err } // Manually restore data restored := &v1beta3.CloudStackMachine{} - if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { + if ok, err := utilconversion.UnmarshalData(r, restored); err != nil || !ok { return err } dst.Spec.FailureDomainName = restored.Spec.FailureDomainName @@ -57,28 +58,29 @@ func (src *CloudStackMachine) ConvertTo(dstRaw conversion.Hub) error { // nolint if restored.Status.Reason != nil { dst.Status.Reason = restored.Status.Reason } + return nil } -func (dst *CloudStackMachine) ConvertFrom(srcRaw conversion.Hub) error { // nolint +func (r *CloudStackMachine) ConvertFrom(srcRaw conversion.Hub) error { src := srcRaw.(*v1beta3.CloudStackMachine) - if err := Convert_v1beta3_CloudStackMachine_To_v1beta1_CloudStackMachine(src, dst, nil); err != nil { + if err := Convert_v1beta3_CloudStackMachine_To_v1beta1_CloudStackMachine(src, r, nil); err != nil { return err } // Preserve Hub data on down-conversion - return utilconversion.MarshalData(src, dst) + return utilconversion.MarshalData(src, r) } -func Convert_v1beta1_CloudStackMachineSpec_To_v1beta3_CloudStackMachineSpec(in *CloudStackMachineSpec, out *v1beta3.CloudStackMachineSpec, scope machineryconversion.Scope) error { // nolint +func Convert_v1beta1_CloudStackMachineSpec_To_v1beta3_CloudStackMachineSpec(in *CloudStackMachineSpec, out *v1beta3.CloudStackMachineSpec, scope machineryconversion.Scope) error { return autoConvert_v1beta1_CloudStackMachineSpec_To_v1beta3_CloudStackMachineSpec(in, out, scope) } -func Convert_v1beta3_CloudStackMachineSpec_To_v1beta1_CloudStackMachineSpec(in *v1beta3.CloudStackMachineSpec, out *CloudStackMachineSpec, s machineryconversion.Scope) error { // nolint +func Convert_v1beta3_CloudStackMachineSpec_To_v1beta1_CloudStackMachineSpec(in *v1beta3.CloudStackMachineSpec, out *CloudStackMachineSpec, s machineryconversion.Scope) error { return autoConvert_v1beta3_CloudStackMachineSpec_To_v1beta1_CloudStackMachineSpec(in, out, s) } -func Convert_v1beta3_CloudStackMachineStatus_To_v1beta1_CloudStackMachineStatus(in *v1beta3.CloudStackMachineStatus, out *CloudStackMachineStatus, s machineryconversion.Scope) error { // nolint +func Convert_v1beta3_CloudStackMachineStatus_To_v1beta1_CloudStackMachineStatus(in *v1beta3.CloudStackMachineStatus, out *CloudStackMachineStatus, s machineryconversion.Scope) error { return autoConvert_v1beta3_CloudStackMachineStatus_To_v1beta1_CloudStackMachineStatus(in, out, s) } diff --git a/api/v1beta1/cloudstackmachine_types.go b/api/v1beta1/cloudstackmachine_types.go index 1bfa7411..b4f9d56c 100644 --- a/api/v1beta1/cloudstackmachine_types.go +++ b/api/v1beta1/cloudstackmachine_types.go @@ -33,7 +33,7 @@ const ( NoAffinity = "no" ) -// CloudStackMachineSpec defines the desired state of CloudStackMachine +// CloudStackMachineSpec defines the desired state of CloudStackMachine. type CloudStackMachineSpec struct { // Name. //+optional @@ -151,6 +151,7 @@ func (s *CloudStackMachineStatus) TimeSinceLastStateChange() time.Duration { if s.InstanceStateLastUpdated.IsZero() { return time.Duration(-1) } + return time.Since(s.InstanceStateLastUpdated.Time) } @@ -163,7 +164,7 @@ func (s *CloudStackMachineStatus) TimeSinceLastStateChange() time.Duration { // +kubebuilder:printcolumn:name="ProviderID",type="string",JSONPath=".spec.providerID",description="CloudStack instance ID" // +kubebuilder:printcolumn:name="Machine",type="string",JSONPath=".metadata.ownerReferences[?(@.kind==\"Machine\")].name",description="Machine object which owns with this CloudStackMachine" -// CloudStackMachine is the Schema for the cloudstackmachines API +// CloudStackMachine is the Schema for the cloudstackmachines API. type CloudStackMachine struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -174,7 +175,7 @@ type CloudStackMachine struct { //+kubebuilder:object:root=true -// CloudStackMachineList contains a list of CloudStackMachine +// CloudStackMachineList contains a list of CloudStackMachine. type CloudStackMachineList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta1/cloudstackmachine_types_test.go b/api/v1beta1/cloudstackmachine_types_test.go index 4344db25..1cd60af5 100644 --- a/api/v1beta1/cloudstackmachine_types_test.go +++ b/api/v1beta1/cloudstackmachine_types_test.go @@ -17,11 +17,13 @@ limitations under the License. package v1beta1_test import ( + "time" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta1" - "time" ) var _ = Describe("CloudStackMachine types", func() { @@ -33,7 +35,7 @@ var _ = Describe("CloudStackMachine types", func() { Context("When calculating time since state change", func() { It("Return the correct value when the last state update time is known", func() { - delta := time.Duration(10 * time.Minute) + delta := 10 * time.Minute lastUpdated := time.Now().Add(-delta) cloudStackMachine.Status.InstanceStateLastUpdated = metav1.NewTime(lastUpdated) Ω(cloudStackMachine.Status.TimeSinceLastStateChange()).Should(BeNumerically("~", delta, time.Second)) diff --git a/api/v1beta1/cloudstackmachinestatechecker_conversion.go b/api/v1beta1/cloudstackmachinestatechecker_conversion.go index fd075c17..9eec6de1 100644 --- a/api/v1beta1/cloudstackmachinestatechecker_conversion.go +++ b/api/v1beta1/cloudstackmachinestatechecker_conversion.go @@ -17,16 +17,19 @@ limitations under the License. package v1beta1 import ( - "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" "sigs.k8s.io/controller-runtime/pkg/conversion" + + "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) -func (src *CloudStackMachineStateChecker) ConvertTo(dstRaw conversion.Hub) error { // nolint +func (r *CloudStackMachineStateChecker) ConvertTo(dstRaw conversion.Hub) error { dst := dstRaw.(*v1beta3.CloudStackMachineStateChecker) - return Convert_v1beta1_CloudStackMachineStateChecker_To_v1beta3_CloudStackMachineStateChecker(src, dst, nil) + + return Convert_v1beta1_CloudStackMachineStateChecker_To_v1beta3_CloudStackMachineStateChecker(r, dst, nil) } -func (dst *CloudStackMachineStateChecker) ConvertFrom(srcRaw conversion.Hub) error { // nolint +func (r *CloudStackMachineStateChecker) ConvertFrom(srcRaw conversion.Hub) error { src := srcRaw.(*v1beta3.CloudStackMachineStateChecker) - return Convert_v1beta3_CloudStackMachineStateChecker_To_v1beta1_CloudStackMachineStateChecker(src, dst, nil) + + return Convert_v1beta3_CloudStackMachineStateChecker_To_v1beta1_CloudStackMachineStateChecker(src, r, nil) } diff --git a/api/v1beta1/cloudstackmachinestatechecker_types.go b/api/v1beta1/cloudstackmachinestatechecker_types.go index 61a6a9b4..cc943168 100644 --- a/api/v1beta1/cloudstackmachinestatechecker_types.go +++ b/api/v1beta1/cloudstackmachinestatechecker_types.go @@ -20,13 +20,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// CloudStackMachineStateCheckerSpec +// CloudStackMachineStateCheckerSpec defines the desired state of CloudStackMachineStateChecker. type CloudStackMachineStateCheckerSpec struct { - // CloudStack machine instance ID + // CloudStack machine instance ID. InstanceID string `json:"instanceID,omitempty"` } -// CloudStackMachineStateCheckerStatus defines the observed state of CloudStackMachineStateChecker +// CloudStackMachineStateCheckerStatus defines the observed state of CloudStackMachineStateChecker. type CloudStackMachineStateCheckerStatus struct { // Reflects the readiness of the Machine State Checker. Ready bool `json:"ready"` @@ -35,7 +35,7 @@ type CloudStackMachineStateCheckerStatus struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -// CloudStackMachineStateChecker is the Schema for the cloudstackmachinestatecheckers API +// CloudStackMachineStateChecker is the Schema for the cloudstackmachinestatecheckers API. type CloudStackMachineStateChecker struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -46,7 +46,7 @@ type CloudStackMachineStateChecker struct { //+kubebuilder:object:root=true -// CloudStackMachineStateCheckerList contains a list of CloudStackMachineStateChecker +// CloudStackMachineStateCheckerList contains a list of CloudStackMachineStateChecker. type CloudStackMachineStateCheckerList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta1/cloudstackmachinetemplate_conversion.go b/api/v1beta1/cloudstackmachinetemplate_conversion.go index bd672f87..52e1bf12 100644 --- a/api/v1beta1/cloudstackmachinetemplate_conversion.go +++ b/api/v1beta1/cloudstackmachinetemplate_conversion.go @@ -19,21 +19,22 @@ package v1beta1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" machineryconversion "k8s.io/apimachinery/pkg/conversion" - "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" utilconversion "sigs.k8s.io/cluster-api/util/conversion" "sigs.k8s.io/controller-runtime/pkg/conversion" + + "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) -func (src *CloudStackMachineTemplate) ConvertTo(dstRaw conversion.Hub) error { // nolint +func (r *CloudStackMachineTemplate) ConvertTo(dstRaw conversion.Hub) error { dst := dstRaw.(*v1beta3.CloudStackMachineTemplate) - if err := Convert_v1beta1_CloudStackMachineTemplate_To_v1beta3_CloudStackMachineTemplate(src, dst, nil); err != nil { + if err := Convert_v1beta1_CloudStackMachineTemplate_To_v1beta3_CloudStackMachineTemplate(r, dst, nil); err != nil { return err } - // Manually restore data + // Manually restore data. restored := &v1beta3.CloudStackMachineTemplate{} - if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { + if ok, err := utilconversion.UnmarshalData(r, restored); err != nil || !ok { return err } if restored.Spec.Template.Spec.FailureDomainName != "" { @@ -48,40 +49,42 @@ func (src *CloudStackMachineTemplate) ConvertTo(dstRaw conversion.Hub) error { / return nil } -func (dst *CloudStackMachineTemplate) ConvertFrom(srcRaw conversion.Hub) error { // nolint +func (r *CloudStackMachineTemplate) ConvertFrom(srcRaw conversion.Hub) error { src := srcRaw.(*v1beta3.CloudStackMachineTemplate) - if err := Convert_v1beta3_CloudStackMachineTemplate_To_v1beta1_CloudStackMachineTemplate(src, dst, nil); err != nil { + if err := Convert_v1beta3_CloudStackMachineTemplate_To_v1beta1_CloudStackMachineTemplate(src, r, nil); err != nil { return err } - // Preserve Hub data on down-conversion - return utilconversion.MarshalData(src, dst) + // Preserve Hub data on down-conversion. + return utilconversion.MarshalData(src, r) } -func Convert_v1beta1_CloudStackMachineTemplateSpec_To_v1beta3_CloudStackMachineTemplateSpec(in *CloudStackMachineTemplateSpec, out *v1beta3.CloudStackMachineTemplateSpec, s machineryconversion.Scope) error { // nolint +func Convert_v1beta1_CloudStackMachineTemplateSpec_To_v1beta3_CloudStackMachineTemplateSpec(in *CloudStackMachineTemplateSpec, out *v1beta3.CloudStackMachineTemplateSpec, s machineryconversion.Scope) error { return Convert_v1beta1_CloudStackMachineTemplateResource_To_v1beta3_CloudStackMachineTemplateResource(&in.Spec, &out.Template, s) } -func Convert_v1beta3_CloudStackMachineTemplateSpec_To_v1beta1_CloudStackMachineTemplateSpec(in *v1beta3.CloudStackMachineTemplateSpec, out *CloudStackMachineTemplateSpec, s machineryconversion.Scope) error { // nolint +func Convert_v1beta3_CloudStackMachineTemplateSpec_To_v1beta1_CloudStackMachineTemplateSpec(in *v1beta3.CloudStackMachineTemplateSpec, out *CloudStackMachineTemplateSpec, s machineryconversion.Scope) error { return Convert_v1beta3_CloudStackMachineTemplateResource_To_v1beta1_CloudStackMachineTemplateResource(&in.Template, &out.Spec, s) } -func Convert_v1beta1_ObjectMeta_To_v1_ObjectMeta(in *clusterv1.ObjectMeta, out *metav1.ObjectMeta, _ machineryconversion.Scope) error { // nolint +func Convert_v1beta1_ObjectMeta_To_v1_ObjectMeta(in *clusterv1.ObjectMeta, out *metav1.ObjectMeta, _ machineryconversion.Scope) error { if in.Annotations != nil { out.Annotations = in.Annotations } if in.Labels != nil { out.Labels = in.Labels } + return nil } -func Convert_v1_ObjectMeta_To_v1beta1_ObjectMeta(in *metav1.ObjectMeta, out *clusterv1.ObjectMeta, _ machineryconversion.Scope) error { // nolint +func Convert_v1_ObjectMeta_To_v1beta1_ObjectMeta(in *metav1.ObjectMeta, out *clusterv1.ObjectMeta, _ machineryconversion.Scope) error { if in.Annotations != nil { out.Annotations = in.Annotations } if in.Labels != nil { out.Labels = in.Labels } + return nil } diff --git a/api/v1beta1/cloudstackmachinetemplate_types.go b/api/v1beta1/cloudstackmachinetemplate_types.go index d8b332df..0b11fe4b 100644 --- a/api/v1beta1/cloudstackmachinetemplate_types.go +++ b/api/v1beta1/cloudstackmachinetemplate_types.go @@ -28,7 +28,7 @@ type CloudStackMachineTemplateResource struct { Spec CloudStackMachineSpec `json:"spec"` } -// CloudStackMachineTemplateSpec defines the desired state of CloudStackMachineTemplate +// CloudStackMachineTemplateSpec defines the desired state of CloudStackMachineTemplate. type CloudStackMachineTemplateSpec struct { Spec CloudStackMachineTemplateResource `json:"template"` } @@ -36,7 +36,7 @@ type CloudStackMachineTemplateSpec struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -// CloudStackMachineTemplate is the Schema for the cloudstackmachinetemplates API +// CloudStackMachineTemplate is the Schema for the cloudstackmachinetemplates API. type CloudStackMachineTemplate struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -46,7 +46,7 @@ type CloudStackMachineTemplate struct { //+kubebuilder:object:root=true -// CloudStackMachineTemplateList contains a list of CloudStackMachineTemplate +// CloudStackMachineTemplateList contains a list of CloudStackMachineTemplate. type CloudStackMachineTemplateList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta1/cloudstackzone_types.go b/api/v1beta1/cloudstackzone_types.go index 4f192fde..87febf49 100644 --- a/api/v1beta1/cloudstackzone_types.go +++ b/api/v1beta1/cloudstackzone_types.go @@ -24,7 +24,7 @@ const ( ZoneFinalizer = "cloudstackzone.infrastructure.cluster.x-k8s.io" ) -// CloudStackZoneSpec defines the desired state of CloudStackZone +// CloudStackZoneSpec defines the desired state of CloudStackZone. type CloudStackZoneSpec struct { // Name. //+optional @@ -38,7 +38,7 @@ type CloudStackZoneSpec struct { Network Network `json:"network"` } -// CloudStackZoneStatus defines the observed state of CloudStackZone +// CloudStackZoneStatus defines the observed state of CloudStackZone. type CloudStackZoneStatus struct { // Reflects the readiness of the CloudStack zone. Ready bool `json:"ready"` @@ -47,7 +47,7 @@ type CloudStackZoneStatus struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +k8s:conversion-gen=false -// CloudStackZone is the Schema for the cloudstackzones API +// CloudStackZone is the Schema for the cloudstackzones API. type CloudStackZone struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -58,7 +58,7 @@ type CloudStackZone struct { //+kubebuilder:object:root=true -// CloudStackZoneList contains a list of CloudStackZone +// CloudStackZoneList contains a list of CloudStackZone. type CloudStackZoneList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta1/conversion.go b/api/v1beta1/conversion.go index 08b48fcb..457faaa5 100644 --- a/api/v1beta1/conversion.go +++ b/api/v1beta1/conversion.go @@ -19,16 +19,17 @@ package v1beta1 import ( "context" "errors" + corev1 "k8s.io/api/core/v1" machineryconversion "k8s.io/apimachinery/pkg/conversion" + "sigs.k8s.io/controller-runtime/pkg/client" + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" - "sigs.k8s.io/controller-runtime/pkg/client" ) const DefaultEndpointCredential = "global" -//nolint:golint,revive,stylecheck func Convert_v1beta1_CloudStackCluster_To_v1beta3_CloudStackCluster(in *CloudStackCluster, out *infrav1.CloudStackCluster, _ machineryconversion.Scope) error { out.ObjectMeta = in.ObjectMeta failureDomains, err := GetFailureDomains(in) @@ -44,10 +45,10 @@ func Convert_v1beta1_CloudStackCluster_To_v1beta3_CloudStackCluster(in *CloudSta FailureDomains: in.Status.FailureDomains, Ready: in.Status.Ready, } + return nil } -//nolint:golint,revive,stylecheck func Convert_v1beta3_CloudStackCluster_To_v1beta1_CloudStackCluster(in *infrav1.CloudStackCluster, out *CloudStackCluster, _ machineryconversion.Scope) error { if len(in.Spec.FailureDomains) < 1 { return errors.New("v1beta3 to v1beta1 conversion not supported when < 1 failure domain is provided") @@ -64,6 +65,7 @@ func Convert_v1beta3_CloudStackCluster_To_v1beta1_CloudStackCluster(in *infrav1. FailureDomains: in.Status.FailureDomains, Ready: in.Status.Ready, } + return nil } @@ -75,9 +77,9 @@ func Convert_v1beta3_Network_To_v1beta1_Network(in *infrav1.Network, out *Networ return nil } -// getZones maps failure domains to zones +// getZones maps failure domains to zones. func getZones(csCluster *infrav1.CloudStackCluster) []Zone { - var zones []Zone + zones := make([]Zone, 0, len(csCluster.Status.FailureDomains)) for _, failureDomain := range csCluster.Spec.FailureDomains { zone := failureDomain.Zone zones = append(zones, Zone{ @@ -90,12 +92,13 @@ func getZones(csCluster *infrav1.CloudStackCluster) []Zone { }, }) } + return zones } // GetFailureDomains maps v1beta1 zones to v1beta3 failure domains. func GetFailureDomains(csCluster *CloudStackCluster) ([]infrav1.CloudStackFailureDomainSpec, error) { - var failureDomains []infrav1.CloudStackFailureDomainSpec + failureDomains := make([]infrav1.CloudStackFailureDomainSpec, 0, len(csCluster.Spec.Zones)) namespace := csCluster.Namespace for _, zone := range csCluster.Spec.Zones { name, err := GetDefaultFailureDomainName(namespace, zone.ID, zone.Name) @@ -121,6 +124,7 @@ func GetFailureDomains(csCluster *CloudStackCluster) ([]infrav1.CloudStackFailur }, }) } + return failureDomains, nil } @@ -140,7 +144,7 @@ func GetDefaultFailureDomainName(namespace string, zoneID string, zoneName strin return "", err } - // try fetch zoneID using zoneName through cloudstack client + // try fetch zoneID using zoneName through cloudstack client. zoneID, err = fetchZoneIDUsingCloudStack(secret, zoneName) if err == nil { return zoneID, nil @@ -148,8 +152,9 @@ func GetDefaultFailureDomainName(namespace string, zoneID string, zoneName strin zoneID, err = fetchZoneIDUsingK8s(namespace, zoneName) if err != nil { - return "", nil + return "", err } + return zoneID, nil } @@ -170,6 +175,7 @@ func fetchZoneIDUsingCloudStack(secret *corev1.Secret, zoneName string) (string, } zone := &infrav1.CloudStackZoneSpec{Name: zoneName} err = client.ResolveZone(zone) + return zone.ID, err } @@ -179,5 +185,6 @@ func GetK8sSecret(name, namespace string) (*corev1.Secret, error) { if err := infrav1.K8sClient.Get(context.TODO(), key, endpointCredentials); err != nil { return nil, err } + return endpointCredentials, nil } diff --git a/api/v1beta1/conversion_test.go b/api/v1beta1/conversion_test.go index 4da5fbe6..7b604764 100644 --- a/api/v1beta1/conversion_test.go +++ b/api/v1beta1/conversion_test.go @@ -22,9 +22,10 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + v1beta1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta1" "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ) var _ = Describe("Conversion", func() { diff --git a/api/v1beta1/doc.go b/api/v1beta1/doc.go index 2de7e2a8..16b6e646 100644 --- a/api/v1beta1/doc.go +++ b/api/v1beta1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package v1beta1 contains API Schema definitions for the infrastructure v1beta1 API group +// Package v1beta1 contains API Schema definitions for the infrastructure v1beta1 API group. // +kubebuilder:object:generate=true // +groupName=infrastructure.cluster.x-k8s.io // +k8s:conversion-gen=sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3 diff --git a/api/v1beta1/groupversion_info.go b/api/v1beta1/groupversion_info.go index 3729b567..ddcb98a8 100644 --- a/api/v1beta1/groupversion_info.go +++ b/api/v1beta1/groupversion_info.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package v1beta1 contains API Schema definitions for the infrastructure v1beta1 API group +// Package v1beta1 contains API Schema definitions for the infrastructure v1beta1 API group. // +kubebuilder:object:generate=true // +groupName=infrastructure.cluster.x-k8s.io package v1beta1 @@ -25,15 +25,15 @@ import ( ) var ( - // GroupVersion is group version used to register these objects + // GroupVersion is group version used to register these objects. GroupVersion = schema.GroupVersion{Group: "infrastructure.cluster.x-k8s.io", Version: "v1beta1"} - // SchemeBuilder is used to add go types to the GroupVersionKind scheme + // SchemeBuilder is used to add go types to the GroupVersionKind scheme. SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} // AddToScheme adds the types in this group-version to the given scheme. AddToScheme = SchemeBuilder.AddToScheme - // So the auto-generated code can compile + // So the auto-generated code can compile. localSchemeBuilder = &SchemeBuilder.SchemeBuilder ) diff --git a/api/v1beta2/cloudstackaffinitygroup_conversion.go b/api/v1beta2/cloudstackaffinitygroup_conversion.go index 6eaa382c..5d5bea26 100644 --- a/api/v1beta2/cloudstackaffinitygroup_conversion.go +++ b/api/v1beta2/cloudstackaffinitygroup_conversion.go @@ -17,16 +17,19 @@ limitations under the License. package v1beta2 import ( - "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" "sigs.k8s.io/controller-runtime/pkg/conversion" + + "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) -func (src *CloudStackAffinityGroup) ConvertTo(dstRaw conversion.Hub) error { // nolint +func (r *CloudStackAffinityGroup) ConvertTo(dstRaw conversion.Hub) error { dst := dstRaw.(*v1beta3.CloudStackAffinityGroup) - return Convert_v1beta2_CloudStackAffinityGroup_To_v1beta3_CloudStackAffinityGroup(src, dst, nil) + + return Convert_v1beta2_CloudStackAffinityGroup_To_v1beta3_CloudStackAffinityGroup(r, dst, nil) } -func (dst *CloudStackAffinityGroup) ConvertFrom(srcRaw conversion.Hub) error { // nolint +func (r *CloudStackAffinityGroup) ConvertFrom(srcRaw conversion.Hub) error { src := srcRaw.(*v1beta3.CloudStackAffinityGroup) - return Convert_v1beta3_CloudStackAffinityGroup_To_v1beta2_CloudStackAffinityGroup(src, dst, nil) + + return Convert_v1beta3_CloudStackAffinityGroup_To_v1beta2_CloudStackAffinityGroup(src, r, nil) } diff --git a/api/v1beta2/cloudstackaffinitygroup_types.go b/api/v1beta2/cloudstackaffinitygroup_types.go index 0d79c4af..cc02b17f 100644 --- a/api/v1beta2/cloudstackaffinitygroup_types.go +++ b/api/v1beta2/cloudstackaffinitygroup_types.go @@ -22,7 +22,7 @@ import ( const AffinityGroupFinalizer = "affinitygroup.infrastructure.cluster.x-k8s.io" -// CloudStackAffinityGroupSpec defines the desired state of CloudStackAffinityGroup +// CloudStackAffinityGroupSpec defines the desired state of CloudStackAffinityGroup. type CloudStackAffinityGroupSpec struct { // Mutually exclusive parameter with AffinityGroupIDs. // Can be "host affinity" or "host anti-affinity". Will create an affinity group per machine set. @@ -40,7 +40,7 @@ type CloudStackAffinityGroupSpec struct { FailureDomainName string `json:"failureDomainName,omitempty"` } -// CloudStackAffinityGroupStatus defines the observed state of CloudStackAffinityGroup +// CloudStackAffinityGroupStatus defines the observed state of CloudStackAffinityGroup. type CloudStackAffinityGroupStatus struct { // Reflects the readiness of the CS Affinity Group. Ready bool `json:"ready"` @@ -49,7 +49,7 @@ type CloudStackAffinityGroupStatus struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -// CloudStackAffinityGroup is the Schema for the cloudstackaffinitygroups API +// CloudStackAffinityGroup is the Schema for the cloudstackaffinitygroups API. type CloudStackAffinityGroup struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -60,7 +60,7 @@ type CloudStackAffinityGroup struct { //+kubebuilder:object:root=true -// CloudStackAffinityGroupList contains a list of CloudStackAffinityGroup +// CloudStackAffinityGroupList contains a list of CloudStackAffinityGroup. type CloudStackAffinityGroupList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta2/cloudstackcluster_conversion.go b/api/v1beta2/cloudstackcluster_conversion.go index ed47000e..8420e42c 100644 --- a/api/v1beta2/cloudstackcluster_conversion.go +++ b/api/v1beta2/cloudstackcluster_conversion.go @@ -19,21 +19,24 @@ package v1beta2 import ( machineryconversion "k8s.io/apimachinery/pkg/conversion" "k8s.io/utils/pointer" - "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" "sigs.k8s.io/controller-runtime/pkg/conversion" + + "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) -func (src *CloudStackCluster) ConvertTo(dstRaw conversion.Hub) error { // nolint +func (r *CloudStackCluster) ConvertTo(dstRaw conversion.Hub) error { dst := dstRaw.(*v1beta3.CloudStackCluster) - return Convert_v1beta2_CloudStackCluster_To_v1beta3_CloudStackCluster(src, dst, nil) + + return Convert_v1beta2_CloudStackCluster_To_v1beta3_CloudStackCluster(r, dst, nil) } -func (dst *CloudStackCluster) ConvertFrom(srcRaw conversion.Hub) error { // nolint +func (r *CloudStackCluster) ConvertFrom(srcRaw conversion.Hub) error { src := srcRaw.(*v1beta3.CloudStackCluster) - return Convert_v1beta3_CloudStackCluster_To_v1beta2_CloudStackCluster(src, dst, nil) + + return Convert_v1beta3_CloudStackCluster_To_v1beta2_CloudStackCluster(src, r, nil) } -func Convert_v1beta3_CloudStackClusterSpec_To_v1beta2_CloudStackClusterSpec(in *v1beta3.CloudStackClusterSpec, out *CloudStackClusterSpec, s machineryconversion.Scope) error { // nolint +func Convert_v1beta3_CloudStackClusterSpec_To_v1beta2_CloudStackClusterSpec(in *v1beta3.CloudStackClusterSpec, out *CloudStackClusterSpec, s machineryconversion.Scope) error { err := autoConvert_v1beta3_CloudStackClusterSpec_To_v1beta2_CloudStackClusterSpec(in, out, s) if err != nil { return err @@ -42,7 +45,7 @@ func Convert_v1beta3_CloudStackClusterSpec_To_v1beta2_CloudStackClusterSpec(in * return nil } -func Convert_v1beta2_CloudStackClusterSpec_To_v1beta3_CloudStackClusterSpec(in *CloudStackClusterSpec, out *v1beta3.CloudStackClusterSpec, s machineryconversion.Scope) error { // nolint +func Convert_v1beta2_CloudStackClusterSpec_To_v1beta3_CloudStackClusterSpec(in *CloudStackClusterSpec, out *v1beta3.CloudStackClusterSpec, s machineryconversion.Scope) error { err := autoConvert_v1beta2_CloudStackClusterSpec_To_v1beta3_CloudStackClusterSpec(in, out, s) if err != nil { return err diff --git a/api/v1beta2/cloudstackcluster_types.go b/api/v1beta2/cloudstackcluster_types.go index 1fe79a1d..07615ab2 100644 --- a/api/v1beta2/cloudstackcluster_types.go +++ b/api/v1beta2/cloudstackcluster_types.go @@ -47,7 +47,7 @@ type CloudStackClusterStatus struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -// CloudStackCluster is the Schema for the cloudstackclusters API +// CloudStackCluster is the Schema for the cloudstackclusters API. type CloudStackCluster struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -60,7 +60,7 @@ type CloudStackCluster struct { //+kubebuilder:object:root=true -// CloudStackClusterList contains a list of CloudStackCluster +// CloudStackClusterList contains a list of CloudStackCluster. type CloudStackClusterList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta2/cloudstackfailuredomain_conversion.go b/api/v1beta2/cloudstackfailuredomain_conversion.go index e6a21273..00d3a7dd 100644 --- a/api/v1beta2/cloudstackfailuredomain_conversion.go +++ b/api/v1beta2/cloudstackfailuredomain_conversion.go @@ -17,16 +17,19 @@ limitations under the License. package v1beta2 import ( - "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" "sigs.k8s.io/controller-runtime/pkg/conversion" + + "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) -func (src *CloudStackFailureDomain) ConvertTo(dstRaw conversion.Hub) error { // nolint +func (r *CloudStackFailureDomain) ConvertTo(dstRaw conversion.Hub) error { dst := dstRaw.(*v1beta3.CloudStackFailureDomain) - return Convert_v1beta2_CloudStackFailureDomain_To_v1beta3_CloudStackFailureDomain(src, dst, nil) + + return Convert_v1beta2_CloudStackFailureDomain_To_v1beta3_CloudStackFailureDomain(r, dst, nil) } -func (dst *CloudStackFailureDomain) ConvertFrom(srcRaw conversion.Hub) error { // nolint +func (r *CloudStackFailureDomain) ConvertFrom(srcRaw conversion.Hub) error { src := srcRaw.(*v1beta3.CloudStackFailureDomain) - return Convert_v1beta3_CloudStackFailureDomain_To_v1beta2_CloudStackFailureDomain(src, dst, nil) + + return Convert_v1beta3_CloudStackFailureDomain_To_v1beta2_CloudStackFailureDomain(src, r, nil) } diff --git a/api/v1beta2/cloudstackfailuredomain_types.go b/api/v1beta2/cloudstackfailuredomain_types.go index cf08f0d2..5af6fa88 100644 --- a/api/v1beta2/cloudstackfailuredomain_types.go +++ b/api/v1beta2/cloudstackfailuredomain_types.go @@ -69,7 +69,7 @@ type CloudStackZoneSpec struct { Network Network `json:"network"` } -// CloudStackFailureDomainSpec defines the desired state of CloudStackFailureDomain +// CloudStackFailureDomainSpec defines the desired state of CloudStackFailureDomain. type CloudStackFailureDomainSpec struct { // The failure domain unique name. Name string `json:"name"` @@ -89,7 +89,7 @@ type CloudStackFailureDomainSpec struct { ACSEndpoint corev1.SecretReference `json:"acsEndpoint"` } -// CloudStackFailureDomainStatus defines the observed state of CloudStackFailureDomain +// CloudStackFailureDomainStatus defines the observed state of CloudStackFailureDomain. type CloudStackFailureDomainStatus struct { // Reflects the readiness of the CloudStack Failure Domain. Ready bool `json:"ready"` @@ -98,7 +98,7 @@ type CloudStackFailureDomainStatus struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -// CloudStackFailureDomain is the Schema for the cloudstackfailuredomains API +// CloudStackFailureDomain is the Schema for the cloudstackfailuredomains API. type CloudStackFailureDomain struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -109,7 +109,7 @@ type CloudStackFailureDomain struct { //+kubebuilder:object:root=true -// CloudStackFailureDomainList contains a list of CloudStackFailureDomain +// CloudStackFailureDomainList contains a list of CloudStackFailureDomain. type CloudStackFailureDomainList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta2/cloudstackisolatednetwork_conversion.go b/api/v1beta2/cloudstackisolatednetwork_conversion.go index 51cccc8d..0c47155a 100644 --- a/api/v1beta2/cloudstackisolatednetwork_conversion.go +++ b/api/v1beta2/cloudstackisolatednetwork_conversion.go @@ -18,18 +18,21 @@ package v1beta2 import ( machineryconversion "k8s.io/apimachinery/pkg/conversion" - "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" "sigs.k8s.io/controller-runtime/pkg/conversion" + + "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) -func (src *CloudStackIsolatedNetwork) ConvertTo(dstRaw conversion.Hub) error { // nolint +func (r *CloudStackIsolatedNetwork) ConvertTo(dstRaw conversion.Hub) error { dst := dstRaw.(*v1beta3.CloudStackIsolatedNetwork) - return Convert_v1beta2_CloudStackIsolatedNetwork_To_v1beta3_CloudStackIsolatedNetwork(src, dst, nil) + + return Convert_v1beta2_CloudStackIsolatedNetwork_To_v1beta3_CloudStackIsolatedNetwork(r, dst, nil) } -func (dst *CloudStackIsolatedNetwork) ConvertFrom(srcRaw conversion.Hub) error { // nolint +func (r *CloudStackIsolatedNetwork) ConvertFrom(srcRaw conversion.Hub) error { src := srcRaw.(*v1beta3.CloudStackIsolatedNetwork) - return Convert_v1beta3_CloudStackIsolatedNetwork_To_v1beta2_CloudStackIsolatedNetwork(src, dst, nil) + + return Convert_v1beta3_CloudStackIsolatedNetwork_To_v1beta2_CloudStackIsolatedNetwork(src, r, nil) } func Convert_v1beta2_CloudStackIsolatedNetworkStatus_To_v1beta3_CloudStackIsolatedNetworkStatus(in *CloudStackIsolatedNetworkStatus, out *v1beta3.CloudStackIsolatedNetworkStatus, s machineryconversion.Scope) error { @@ -37,6 +40,7 @@ func Convert_v1beta2_CloudStackIsolatedNetworkStatus_To_v1beta3_CloudStackIsolat out.LBRuleID = in.LBRuleID out.LoadBalancerRuleIDs = []string{in.LBRuleID} out.Ready = in.Ready + return nil } @@ -44,5 +48,6 @@ func Convert_v1beta3_CloudStackIsolatedNetworkStatus_To_v1beta2_CloudStackIsolat out.PublicIPID = in.PublicIPID out.LBRuleID = in.LBRuleID out.Ready = in.Ready + return nil } diff --git a/api/v1beta2/cloudstackisolatednetwork_types.go b/api/v1beta2/cloudstackisolatednetwork_types.go index 938bf4c1..ff690347 100644 --- a/api/v1beta2/cloudstackisolatednetwork_types.go +++ b/api/v1beta2/cloudstackisolatednetwork_types.go @@ -24,7 +24,7 @@ import ( // The presence of a finalizer prevents CAPI from deleting the corresponding CAPI data. const IsolatedNetworkFinalizer = "cloudstackisolatednetwork.infrastructure.cluster.x-k8s.io" -// CloudStackIsolatedNetworkSpec defines the desired state of CloudStackIsolatedNetwork +// CloudStackIsolatedNetworkSpec defines the desired state of CloudStackIsolatedNetwork. type CloudStackIsolatedNetworkSpec struct { // Name. //+optional @@ -41,7 +41,7 @@ type CloudStackIsolatedNetworkSpec struct { FailureDomainName string `json:"failureDomainName"` } -// CloudStackIsolatedNetworkStatus defines the observed state of CloudStackIsolatedNetwork +// CloudStackIsolatedNetworkStatus defines the observed state of CloudStackIsolatedNetwork. type CloudStackIsolatedNetworkStatus struct { // The CS public IP ID to use for the k8s endpoint. PublicIPID string `json:"publicIPID,omitempty"` @@ -53,17 +53,18 @@ type CloudStackIsolatedNetworkStatus struct { Ready bool `json:"ready"` } -func (n *CloudStackIsolatedNetwork) Network() *Network { +func (r *CloudStackIsolatedNetwork) Network() *Network { return &Network{ - Name: n.Spec.Name, + Name: r.Spec.Name, Type: "IsolatedNetwork", - ID: n.Spec.ID} + ID: r.Spec.ID, + } } //+kubebuilder:object:root=true //+kubebuilder:subresource:status -// CloudStackIsolatedNetwork is the Schema for the cloudstackisolatednetworks API +// CloudStackIsolatedNetwork is the Schema for the cloudstackisolatednetworks API. type CloudStackIsolatedNetwork struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -74,7 +75,7 @@ type CloudStackIsolatedNetwork struct { //+kubebuilder:object:root=true -// CloudStackIsolatedNetworkList contains a list of CloudStackIsolatedNetwork +// CloudStackIsolatedNetworkList contains a list of CloudStackIsolatedNetwork. type CloudStackIsolatedNetworkList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta2/cloudstackmachine_conversion.go b/api/v1beta2/cloudstackmachine_conversion.go index c5717b5f..df81357a 100644 --- a/api/v1beta2/cloudstackmachine_conversion.go +++ b/api/v1beta2/cloudstackmachine_conversion.go @@ -18,24 +18,25 @@ package v1beta2 import ( machineryconversion "k8s.io/apimachinery/pkg/conversion" - "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" utilconversion "sigs.k8s.io/cluster-api/util/conversion" "sigs.k8s.io/controller-runtime/pkg/conversion" + + "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) -func (src *CloudStackMachine) ConvertTo(dstRaw conversion.Hub) error { +func (r *CloudStackMachine) ConvertTo(dstRaw conversion.Hub) error { dst := dstRaw.(*v1beta3.CloudStackMachine) - if err := Convert_v1beta2_CloudStackMachine_To_v1beta3_CloudStackMachine(src, dst, nil); err != nil { + if err := Convert_v1beta2_CloudStackMachine_To_v1beta3_CloudStackMachine(r, dst, nil); err != nil { return err } - // Manually restore data + // Manually restore data. restored := &v1beta3.CloudStackMachine{} - if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { + if ok, err := utilconversion.UnmarshalData(r, restored); err != nil || !ok { return err } - // Don't bother converting empty disk offering objects + // Don't bother converting empty disk offering objects. if restored.Spec.DiskOffering.MountPath != "" { dst.Spec.DiskOffering = &v1beta3.CloudStackResourceDiskOffering{ CustomSize: restored.Spec.DiskOffering.CustomSize, @@ -47,24 +48,25 @@ func (src *CloudStackMachine) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.DiskOffering.ID = restored.Spec.DiskOffering.ID dst.Spec.DiskOffering.Name = restored.Spec.DiskOffering.Name } + return nil } -func (dst *CloudStackMachine) ConvertFrom(srcRaw conversion.Hub) error { // nolint +func (r *CloudStackMachine) ConvertFrom(srcRaw conversion.Hub) error { src := srcRaw.(*v1beta3.CloudStackMachine) - if err := Convert_v1beta3_CloudStackMachine_To_v1beta2_CloudStackMachine(src, dst, nil); err != nil { + if err := Convert_v1beta3_CloudStackMachine_To_v1beta2_CloudStackMachine(src, r, nil); err != nil { return err } - // Preserve Hub data on down-conversion - return utilconversion.MarshalData(src, dst) + // Preserve Hub data on down-conversion. + return utilconversion.MarshalData(src, r) } -func Convert_v1beta3_CloudStackMachineSpec_To_v1beta2_CloudStackMachineSpec(in *v1beta3.CloudStackMachineSpec, out *CloudStackMachineSpec, s machineryconversion.Scope) error { // nolint +func Convert_v1beta3_CloudStackMachineSpec_To_v1beta2_CloudStackMachineSpec(in *v1beta3.CloudStackMachineSpec, out *CloudStackMachineSpec, s machineryconversion.Scope) error { return autoConvert_v1beta3_CloudStackMachineSpec_To_v1beta2_CloudStackMachineSpec(in, out, s) } -func Convert_v1beta2_CloudStackMachineSpec_To_v1beta3_CloudStackMachineSpec(in *CloudStackMachineSpec, out *v1beta3.CloudStackMachineSpec, s machineryconversion.Scope) error { // nolint +func Convert_v1beta2_CloudStackMachineSpec_To_v1beta3_CloudStackMachineSpec(in *CloudStackMachineSpec, out *v1beta3.CloudStackMachineSpec, s machineryconversion.Scope) error { return autoConvert_v1beta2_CloudStackMachineSpec_To_v1beta3_CloudStackMachineSpec(in, out, s) } diff --git a/api/v1beta2/cloudstackmachine_types.go b/api/v1beta2/cloudstackmachine_types.go index 36ff007f..0a3c9e9e 100644 --- a/api/v1beta2/cloudstackmachine_types.go +++ b/api/v1beta2/cloudstackmachine_types.go @@ -34,7 +34,7 @@ const ( NoAffinity = "no" ) -// CloudStackMachineSpec defines the desired state of CloudStackMachine +// CloudStackMachineSpec defines the desired state of CloudStackMachine. type CloudStackMachineSpec struct { // Name. //+optional @@ -93,8 +93,8 @@ type CloudStackMachineSpec struct { UncompressedUserData *bool `json:"uncompressedUserData,omitempty"` } -func (c *CloudStackMachine) CompressUserdata() bool { - return c.Spec.UncompressedUserData == nil || !*c.Spec.UncompressedUserData +func (r *CloudStackMachine) CompressUserdata() bool { + return r.Spec.UncompressedUserData == nil || !*r.Spec.UncompressedUserData } type CloudStackResourceIdentifier struct { @@ -153,6 +153,7 @@ func (s *CloudStackMachineStatus) TimeSinceLastStateChange() time.Duration { if s.InstanceStateLastUpdated.IsZero() { return time.Duration(-1) } + return time.Since(s.InstanceStateLastUpdated.Time) } @@ -165,7 +166,7 @@ func (s *CloudStackMachineStatus) TimeSinceLastStateChange() time.Duration { // +kubebuilder:printcolumn:name="ProviderID",type="string",JSONPath=".spec.providerID",description="CloudStack instance ID" // +kubebuilder:printcolumn:name="Machine",type="string",JSONPath=".metadata.ownerReferences[?(@.kind==\"Machine\")].name",description="Machine object which owns with this CloudStackMachine" -// CloudStackMachine is the Schema for the cloudstackmachines API +// CloudStackMachine is the Schema for the cloudstackmachines API. type CloudStackMachine struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -176,7 +177,7 @@ type CloudStackMachine struct { //+kubebuilder:object:root=true -// CloudStackMachineList contains a list of CloudStackMachine +// CloudStackMachineList contains a list of CloudStackMachine. type CloudStackMachineList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta2/cloudstackmachine_types_test.go b/api/v1beta2/cloudstackmachine_types_test.go index 3d3750db..e6c30b3a 100644 --- a/api/v1beta2/cloudstackmachine_types_test.go +++ b/api/v1beta2/cloudstackmachine_types_test.go @@ -17,11 +17,11 @@ limitations under the License. package v1beta2_test import ( - "k8s.io/utils/pointer" - capcv1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta2" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "k8s.io/utils/pointer" + + capcv1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta2" ) var _ = Describe("CloudStackMachineConfig_CompressUserdata", func() { @@ -58,10 +58,10 @@ var _ = Describe("CloudStackMachineConfig_CompressUserdata", func() { Expect: true, }, } { - tc := tc - It(tc.Name, func() { - result := tc.Machine.CompressUserdata() - Expect(result).To(Equal(tc.Expect)) + tcc := tc + It(tcc.Name, func() { + result := tcc.Machine.CompressUserdata() + Expect(result).To(Equal(tcc.Expect)) }) } }) diff --git a/api/v1beta2/cloudstackmachinestatechecker_conversion.go b/api/v1beta2/cloudstackmachinestatechecker_conversion.go index 182f45d9..d8441926 100644 --- a/api/v1beta2/cloudstackmachinestatechecker_conversion.go +++ b/api/v1beta2/cloudstackmachinestatechecker_conversion.go @@ -17,16 +17,19 @@ limitations under the License. package v1beta2 import ( - "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" "sigs.k8s.io/controller-runtime/pkg/conversion" + + "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) -func (src *CloudStackMachineStateChecker) ConvertTo(dstRaw conversion.Hub) error { // nolint +func (r *CloudStackMachineStateChecker) ConvertTo(dstRaw conversion.Hub) error { dst := dstRaw.(*v1beta3.CloudStackMachineStateChecker) - return Convert_v1beta2_CloudStackMachineStateChecker_To_v1beta3_CloudStackMachineStateChecker(src, dst, nil) + + return Convert_v1beta2_CloudStackMachineStateChecker_To_v1beta3_CloudStackMachineStateChecker(r, dst, nil) } -func (dst *CloudStackMachineStateChecker) ConvertFrom(srcRaw conversion.Hub) error { // nolint +func (r *CloudStackMachineStateChecker) ConvertFrom(srcRaw conversion.Hub) error { src := srcRaw.(*v1beta3.CloudStackMachineStateChecker) - return Convert_v1beta3_CloudStackMachineStateChecker_To_v1beta2_CloudStackMachineStateChecker(src, dst, nil) + + return Convert_v1beta3_CloudStackMachineStateChecker_To_v1beta2_CloudStackMachineStateChecker(src, r, nil) } diff --git a/api/v1beta2/cloudstackmachinestatechecker_types.go b/api/v1beta2/cloudstackmachinestatechecker_types.go index 3ecb5cdf..0157e3b2 100644 --- a/api/v1beta2/cloudstackmachinestatechecker_types.go +++ b/api/v1beta2/cloudstackmachinestatechecker_types.go @@ -18,13 +18,13 @@ package v1beta2 import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -// CloudStackMachineStateCheckerSpec +// CloudStackMachineStateCheckerSpec defines the desired state of CloudStackMachineStateChecker. type CloudStackMachineStateCheckerSpec struct { - // CloudStack machine instance ID + // CloudStack machine instance ID. InstanceID string `json:"instanceID,omitempty"` } -// CloudStackMachineStateCheckerStatus defines the observed state of CloudStackMachineStateChecker +// CloudStackMachineStateCheckerStatus defines the observed state of CloudStackMachineStateChecker. type CloudStackMachineStateCheckerStatus struct { // Reflects the readiness of the Machine State Checker. Ready bool `json:"ready"` @@ -33,7 +33,7 @@ type CloudStackMachineStateCheckerStatus struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -// CloudStackMachineStateChecker is the Schema for the cloudstackmachinestatecheckers API +// CloudStackMachineStateChecker is the Schema for the cloudstackmachinestatecheckers API. type CloudStackMachineStateChecker struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -44,7 +44,7 @@ type CloudStackMachineStateChecker struct { //+kubebuilder:object:root=true -// CloudStackMachineStateCheckerList contains a list of CloudStackMachineStateChecker +// CloudStackMachineStateCheckerList contains a list of CloudStackMachineStateChecker. type CloudStackMachineStateCheckerList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta2/cloudstackmachinetemplate_conversion.go b/api/v1beta2/cloudstackmachinetemplate_conversion.go index 7621936b..736fd1ca 100644 --- a/api/v1beta2/cloudstackmachinetemplate_conversion.go +++ b/api/v1beta2/cloudstackmachinetemplate_conversion.go @@ -19,21 +19,22 @@ package v1beta2 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" machineryconversion "k8s.io/apimachinery/pkg/conversion" - "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" utilconversion "sigs.k8s.io/cluster-api/util/conversion" "sigs.k8s.io/controller-runtime/pkg/conversion" + + "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) -func (src *CloudStackMachineTemplate) ConvertTo(dstRaw conversion.Hub) error { // nolint +func (r *CloudStackMachineTemplate) ConvertTo(dstRaw conversion.Hub) error { dst := dstRaw.(*v1beta3.CloudStackMachineTemplate) - if err := Convert_v1beta2_CloudStackMachineTemplate_To_v1beta3_CloudStackMachineTemplate(src, dst, nil); err != nil { + if err := Convert_v1beta2_CloudStackMachineTemplate_To_v1beta3_CloudStackMachineTemplate(r, dst, nil); err != nil { return err } - // Manually restore data + // Manually restore data. restored := &v1beta3.CloudStackMachineTemplate{} - if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { + if ok, err := utilconversion.UnmarshalData(r, restored); err != nil || !ok { return err } if restored.Spec.Template.Spec.FailureDomainName != "" { @@ -48,35 +49,38 @@ func (src *CloudStackMachineTemplate) ConvertTo(dstRaw conversion.Hub) error { / return nil } -func (dst *CloudStackMachineTemplate) ConvertFrom(srcRaw conversion.Hub) error { // nolint +func (r *CloudStackMachineTemplate) ConvertFrom(srcRaw conversion.Hub) error { src := srcRaw.(*v1beta3.CloudStackMachineTemplate) - return Convert_v1beta3_CloudStackMachineTemplate_To_v1beta2_CloudStackMachineTemplate(src, dst, nil) + + return Convert_v1beta3_CloudStackMachineTemplate_To_v1beta2_CloudStackMachineTemplate(src, r, nil) } -func Convert_v1beta2_CloudStackMachineTemplateSpec_To_v1beta3_CloudStackMachineTemplateSpec(in *CloudStackMachineTemplateSpec, out *v1beta3.CloudStackMachineTemplateSpec, s machineryconversion.Scope) error { // nolint +func Convert_v1beta2_CloudStackMachineTemplateSpec_To_v1beta3_CloudStackMachineTemplateSpec(in *CloudStackMachineTemplateSpec, out *v1beta3.CloudStackMachineTemplateSpec, s machineryconversion.Scope) error { return Convert_v1beta2_CloudStackMachineTemplateResource_To_v1beta3_CloudStackMachineTemplateResource(&in.Spec, &out.Template, s) } -func Convert_v1beta3_CloudStackMachineTemplateSpec_To_v1beta2_CloudStackMachineTemplateSpec(in *v1beta3.CloudStackMachineTemplateSpec, out *CloudStackMachineTemplateSpec, s machineryconversion.Scope) error { // nolint +func Convert_v1beta3_CloudStackMachineTemplateSpec_To_v1beta2_CloudStackMachineTemplateSpec(in *v1beta3.CloudStackMachineTemplateSpec, out *CloudStackMachineTemplateSpec, s machineryconversion.Scope) error { return Convert_v1beta3_CloudStackMachineTemplateResource_To_v1beta2_CloudStackMachineTemplateResource(&in.Template, &out.Spec, s) } -func Convert_v1beta1_ObjectMeta_To_v1_ObjectMeta(in *clusterv1.ObjectMeta, out *metav1.ObjectMeta, _ machineryconversion.Scope) error { // nolint +func Convert_v1beta1_ObjectMeta_To_v1_ObjectMeta(in *clusterv1.ObjectMeta, out *metav1.ObjectMeta, _ machineryconversion.Scope) error { if in.Annotations != nil { out.Annotations = in.Annotations } if in.Labels != nil { out.Labels = in.Labels } + return nil } -func Convert_v1_ObjectMeta_To_v1beta1_ObjectMeta(in *metav1.ObjectMeta, out *clusterv1.ObjectMeta, _ machineryconversion.Scope) error { // nolint +func Convert_v1_ObjectMeta_To_v1beta1_ObjectMeta(in *metav1.ObjectMeta, out *clusterv1.ObjectMeta, _ machineryconversion.Scope) error { if in.Annotations != nil { out.Annotations = in.Annotations } if in.Labels != nil { out.Labels = in.Labels } + return nil } diff --git a/api/v1beta2/cloudstackmachinetemplate_types.go b/api/v1beta2/cloudstackmachinetemplate_types.go index c8c3b22c..31de2a3f 100644 --- a/api/v1beta2/cloudstackmachinetemplate_types.go +++ b/api/v1beta2/cloudstackmachinetemplate_types.go @@ -27,7 +27,7 @@ type CloudStackMachineTemplateResource struct { Spec CloudStackMachineSpec `json:"spec"` } -// CloudStackMachineTemplateSpec defines the desired state of CloudStackMachineTemplate +// CloudStackMachineTemplateSpec defines the desired state of CloudStackMachineTemplate. type CloudStackMachineTemplateSpec struct { Spec CloudStackMachineTemplateResource `json:"template"` } @@ -35,7 +35,7 @@ type CloudStackMachineTemplateSpec struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -// CloudStackMachineTemplate is the Schema for the cloudstackmachinetemplates API +// CloudStackMachineTemplate is the Schema for the cloudstackmachinetemplates API. type CloudStackMachineTemplate struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -45,7 +45,7 @@ type CloudStackMachineTemplate struct { //+kubebuilder:object:root=true -// CloudStackMachineTemplateList contains a list of CloudStackMachineTemplate +// CloudStackMachineTemplateList contains a list of CloudStackMachineTemplate. type CloudStackMachineTemplateList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta2/conversion.go b/api/v1beta2/conversion.go index 8407733e..09042f9c 100644 --- a/api/v1beta2/conversion.go +++ b/api/v1beta2/conversion.go @@ -18,6 +18,7 @@ package v1beta2 import ( machineryconversion "k8s.io/apimachinery/pkg/conversion" + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) diff --git a/api/v1beta2/doc.go b/api/v1beta2/doc.go index 9e24cab0..32953e93 100644 --- a/api/v1beta2/doc.go +++ b/api/v1beta2/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package v1beta2 contains API Schema definitions for the infrastructure v1beta2 API group +// Package v1beta2 contains API Schema definitions for the infrastructure v1beta2 API group. // +kubebuilder:object:generate=true // +groupName=infrastructure.cluster.x-k8s.io // +k8s:conversion-gen=sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3 diff --git a/api/v1beta2/groupversion_info.go b/api/v1beta2/groupversion_info.go index 36759791..5087e545 100644 --- a/api/v1beta2/groupversion_info.go +++ b/api/v1beta2/groupversion_info.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package v1beta2 contains API Schema definitions for the infrastructure v1beta2 API group +// Package v1beta2 contains API Schema definitions for the infrastructure v1beta2 API group. // +kubebuilder:object:generate=true // +groupName=infrastructure.cluster.x-k8s.io package v1beta2 @@ -25,10 +25,10 @@ import ( ) var ( - // GroupVersion is group version used to register these objects + // GroupVersion is group version used to register these objects. GroupVersion = schema.GroupVersion{Group: "infrastructure.cluster.x-k8s.io", Version: "v1beta2"} - // SchemeBuilder is used to add go types to the GroupVersionKind scheme + // SchemeBuilder is used to add go types to the GroupVersionKind scheme. SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} // AddToScheme adds the types in this group-version to the given scheme. diff --git a/api/v1beta3/cloudstackaffinitygroup_types.go b/api/v1beta3/cloudstackaffinitygroup_types.go index 961f2e85..2cc4ec99 100644 --- a/api/v1beta3/cloudstackaffinitygroup_types.go +++ b/api/v1beta3/cloudstackaffinitygroup_types.go @@ -22,7 +22,7 @@ import ( const AffinityGroupFinalizer = "affinitygroup.infrastructure.cluster.x-k8s.io" -// CloudStackAffinityGroupSpec defines the desired state of CloudStackAffinityGroup +// CloudStackAffinityGroupSpec defines the desired state of CloudStackAffinityGroup. type CloudStackAffinityGroupSpec struct { // Mutually exclusive parameter with AffinityGroupIDs. // Can be "host affinity", "host anti-affinity", "non-strict host affinity" or "non-strict host anti-affinity". Will create an affinity group per machine set. @@ -40,7 +40,7 @@ type CloudStackAffinityGroupSpec struct { FailureDomainName string `json:"failureDomainName,omitempty"` } -// CloudStackAffinityGroupStatus defines the observed state of CloudStackAffinityGroup +// CloudStackAffinityGroupStatus defines the observed state of CloudStackAffinityGroup. type CloudStackAffinityGroupStatus struct { // Reflects the readiness of the CS Affinity Group. //+optional @@ -51,7 +51,7 @@ type CloudStackAffinityGroupStatus struct { //+kubebuilder:subresource:status //+kubebuilder:storageversion -// CloudStackAffinityGroup is the Schema for the cloudstackaffinitygroups API +// CloudStackAffinityGroup is the Schema for the cloudstackaffinitygroups API. type CloudStackAffinityGroup struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -62,7 +62,7 @@ type CloudStackAffinityGroup struct { //+kubebuilder:object:root=true -// CloudStackAffinityGroupList contains a list of CloudStackAffinityGroup +// CloudStackAffinityGroupList contains a list of CloudStackAffinityGroup. type CloudStackAffinityGroupList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta3/cloudstackcluster_types.go b/api/v1beta3/cloudstackcluster_types.go index 837dd0eb..fdac13f1 100644 --- a/api/v1beta3/cloudstackcluster_types.go +++ b/api/v1beta3/cloudstackcluster_types.go @@ -57,7 +57,7 @@ type CloudStackClusterStatus struct { //+kubebuilder:subresource:status //+kubebuilder:storageversion -// CloudStackCluster is the Schema for the cloudstackclusters API +// CloudStackCluster is the Schema for the cloudstackclusters API. type CloudStackCluster struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -70,7 +70,7 @@ type CloudStackCluster struct { //+kubebuilder:object:root=true -// CloudStackClusterList contains a list of CloudStackCluster +// CloudStackClusterList contains a list of CloudStackCluster. type CloudStackClusterList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta3/cloudstackcluster_webhook.go b/api/v1beta3/cloudstackcluster_webhook.go index 0ec6af1b..23eedca0 100644 --- a/api/v1beta3/cloudstackcluster_webhook.go +++ b/api/v1beta3/cloudstackcluster_webhook.go @@ -24,12 +24,13 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" - "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/webhookutil" "sigs.k8s.io/cluster-api/util/annotations" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/webhookutil" ) // log is for logging in this package. @@ -49,13 +50,13 @@ var ( _ webhook.Validator = &CloudStackCluster{} ) -// Default implements webhook.Defaulter so a webhook will be registered for the type +// Default implements webhook.Defaulter so a webhook will be registered for the type. func (r *CloudStackCluster) Default() { cloudstackclusterlog.V(1).Info("entered api default setting webhook", "api resource name", r.Name) // No defaulted values supported yet. } -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. func (r *CloudStackCluster) ValidateCreate() (admission.Warnings, error) { cloudstackclusterlog.V(1).Info("entered validate create webhook", "api resource name", r.Name) @@ -83,7 +84,7 @@ func (r *CloudStackCluster) ValidateCreate() (admission.Warnings, error) { if fdSpec.Zone.Network.CIDR != "" { if _, errMsg := ValidateCIDR(fdSpec.Zone.Network.CIDR); errMsg != nil { errorList = append(errorList, field.Invalid( - field.NewPath("spec", "failureDomains", "Zone", "Network"), fdSpec.Zone.Network.CIDR, fmt.Sprintf("must be valid CIDR: %s", errMsg.Error()))) + field.NewPath("spec", "failureDomains", "Zone", "Network"), fdSpec.Zone.Network.CIDR, "must be valid CIDR: "+errMsg.Error())) } } if fdSpec.Zone.Network.Domain != "" { @@ -98,7 +99,7 @@ func (r *CloudStackCluster) ValidateCreate() (admission.Warnings, error) { return nil, webhookutil.AggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, errorList) } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. func (r *CloudStackCluster) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { cloudstackclusterlog.V(1).Info("entered validate update webhook", "api resource name", r.Name) @@ -134,7 +135,7 @@ func (r *CloudStackCluster) ValidateUpdate(old runtime.Object) (admission.Warnin return nil, webhookutil.AggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, errorList) } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type. func (r *CloudStackCluster) ValidateDelete() (admission.Warnings, error) { cloudstackclusterlog.V(1).Info("entered validate delete webhook", "api resource name", r.Name) // No deletion validations. Deletion webhook not enabled. @@ -155,13 +156,14 @@ func ValidateFailureDomainUpdates(oldFDs, newFDs []CloudStackFailureDomainSpec) atLeastOneRemains = true if !FailureDomainsEqual(newFD, oldFD) { return field.Forbidden(field.NewPath("spec", "FailureDomains"), - fmt.Sprintf("Cannot change FailureDomain %s", oldFD.Name)) + "Cannot change FailureDomain "+oldFD.Name) } } } if !atLeastOneRemains { return field.Forbidden(field.NewPath("spec", "FailureDomains"), "At least one FailureDomain must be unchanged on update.") } + return nil } @@ -179,11 +181,12 @@ func FailureDomainsEqual(fd1, fd2 CloudStackFailureDomainSpec) bool { fd1.Zone.Network.Domain == fd2.Zone.Network.Domain } -// ValidateCIDR validates whether a CIDR matches the conventions expected by net.ParseCIDR +// ValidateCIDR validates whether a CIDR matches the conventions expected by net.ParseCIDR. func ValidateCIDR(cidr string) (*net.IPNet, error) { _, net, err := net.ParseCIDR(cidr) if err != nil { return nil, err } + return net, nil } diff --git a/api/v1beta3/cloudstackcluster_webhook_test.go b/api/v1beta3/cloudstackcluster_webhook_test.go index bfcdb5e0..af1fb304 100644 --- a/api/v1beta3/cloudstackcluster_webhook_test.go +++ b/api/v1beta3/cloudstackcluster_webhook_test.go @@ -21,17 +21,21 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" - dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/util/annotations" + + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" + dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" +) + +const ( + forbiddenRegex = "admission webhook.*denied the request.*Forbidden\\: %s" + invalidRegex = "admission webhook.*denied the request.*Invalid value\\: \".*\"\\: %s" + requiredRegex = "admission webhook.*denied the request.*Required value\\: %s" ) var _ = Describe("CloudStackCluster webhooks", func() { var ctx context.Context - forbiddenRegex := "admission webhook.*denied the request.*Forbidden\\: %s" - invalidRegex := "admission webhook.*denied the request.*Invalid value\\: \".*\"\\: %s" - requiredRegex := "admission webhook.*denied the request.*Required value\\: %s" BeforeEach(func() { // Reset test vars to initial state. ctx = context.Background() @@ -105,11 +109,11 @@ var _ = Describe("CloudStackCluster webhooks", func() { Context("When updating a CloudStackCluster's annotations", func() { It("Should reject removal of externally managed ('managed-by') annotation from CloudStackCluster", func() { - // Create a CloudStackCluster with managed-by annotation + // Create a CloudStackCluster with managed-by annotation. annotations.AddAnnotations(dummies.CSCluster, map[string]string{clusterv1.ManagedByAnnotation: ""}) Ω(k8sClient.Create(ctx, dummies.CSCluster)).Should(Succeed()) - // Remove the annotation and update CloudStackCluster + // Remove the annotation and update CloudStackCluster. dummies.CSCluster.Annotations = make(map[string]string) Ω(k8sClient.Update(ctx, dummies.CSCluster)). Should(MatchError(MatchRegexp(forbiddenRegex, "removal of externally managed"))) diff --git a/api/v1beta3/cloudstackfailuredomain_types.go b/api/v1beta3/cloudstackfailuredomain_types.go index 893f0f88..a757dd4f 100644 --- a/api/v1beta3/cloudstackfailuredomain_types.go +++ b/api/v1beta3/cloudstackfailuredomain_types.go @@ -77,7 +77,7 @@ type CloudStackZoneSpec struct { Network Network `json:"network"` } -// CloudStackFailureDomainSpec defines the desired state of CloudStackFailureDomain +// CloudStackFailureDomainSpec defines the desired state of CloudStackFailureDomain. type CloudStackFailureDomainSpec struct { // The failure domain unique name. Name string `json:"name"` @@ -97,7 +97,7 @@ type CloudStackFailureDomainSpec struct { ACSEndpoint corev1.SecretReference `json:"acsEndpoint"` } -// CloudStackFailureDomainStatus defines the observed state of CloudStackFailureDomain +// CloudStackFailureDomainStatus defines the observed state of CloudStackFailureDomain. type CloudStackFailureDomainStatus struct { // Reflects the readiness of the CloudStack Failure Domain. //+optional @@ -108,7 +108,7 @@ type CloudStackFailureDomainStatus struct { //+kubebuilder:subresource:status //+kubebuilder:storageversion -// CloudStackFailureDomain is the Schema for the cloudstackfailuredomains API +// CloudStackFailureDomain is the Schema for the cloudstackfailuredomains API. type CloudStackFailureDomain struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -119,7 +119,7 @@ type CloudStackFailureDomain struct { //+kubebuilder:object:root=true -// CloudStackFailureDomainList contains a list of CloudStackFailureDomain +// CloudStackFailureDomainList contains a list of CloudStackFailureDomain. type CloudStackFailureDomainList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta3/cloudstackisolatednetwork_types.go b/api/v1beta3/cloudstackisolatednetwork_types.go index 7c2f45ce..12dec009 100644 --- a/api/v1beta3/cloudstackisolatednetwork_types.go +++ b/api/v1beta3/cloudstackisolatednetwork_types.go @@ -24,7 +24,7 @@ import ( // The presence of a finalizer prevents CAPI from deleting the corresponding CAPI data. const IsolatedNetworkFinalizer = "cloudstackisolatednetwork.infrastructure.cluster.x-k8s.io" -// CloudStackIsolatedNetworkSpec defines the desired state of CloudStackIsolatedNetwork +// CloudStackIsolatedNetworkSpec defines the desired state of CloudStackIsolatedNetwork. type CloudStackIsolatedNetworkSpec struct { // Name. //+optional @@ -49,7 +49,7 @@ type CloudStackIsolatedNetworkSpec struct { Domain string `json:"domain,omitempty"` } -// CloudStackIsolatedNetworkStatus defines the observed state of CloudStackIsolatedNetwork +// CloudStackIsolatedNetworkStatus defines the observed state of CloudStackIsolatedNetwork. type CloudStackIsolatedNetworkStatus struct { // The outgoing IP of the isolated network. PublicIPAddress string `json:"publicIPAddress,omitempty"` @@ -77,14 +77,15 @@ func (n *CloudStackIsolatedNetwork) Network() *Network { return &Network{ Name: n.Spec.Name, Type: "IsolatedNetwork", - ID: n.Spec.ID} + ID: n.Spec.ID, + } } //+kubebuilder:object:root=true //+kubebuilder:subresource:status //+kubebuilder:storageversion -// CloudStackIsolatedNetwork is the Schema for the cloudstackisolatednetworks API +// CloudStackIsolatedNetwork is the Schema for the cloudstackisolatednetworks API. type CloudStackIsolatedNetwork struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -95,7 +96,7 @@ type CloudStackIsolatedNetwork struct { //+kubebuilder:object:root=true -// CloudStackIsolatedNetworkList contains a list of CloudStackIsolatedNetwork +// CloudStackIsolatedNetworkList contains a list of CloudStackIsolatedNetwork. type CloudStackIsolatedNetworkList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta3/cloudstackmachine_types.go b/api/v1beta3/cloudstackmachine_types.go index 4a43c087..86b6901d 100644 --- a/api/v1beta3/cloudstackmachine_types.go +++ b/api/v1beta3/cloudstackmachine_types.go @@ -27,14 +27,14 @@ import ( const MachineFinalizer = "cloudstackmachine.infrastructure.cluster.x-k8s.io" const ( - ProAffinity = "pro" - AntiAffinity = "anti" - SoftProAffinity = "soft-pro" - SoftAntiAffinity = "soft-anti" - NoAffinity = "no" + AffinityTypePro = "pro" + AffinityTypeAnti = "anti" + AffinityTypeSoftPro = "soft-pro" + AffinityTypeSoftAnti = "soft-anti" + AffinityTypeNo = "no" ) -// CloudStackMachineSpec defines the desired state of CloudStackMachine +// CloudStackMachineSpec defines the desired state of CloudStackMachine. type CloudStackMachineSpec struct { // Name. //+optional @@ -87,14 +87,14 @@ type CloudStackMachineSpec struct { FailureDomainName string `json:"failureDomainName,omitempty"` // UncompressedUserData specifies whether the user data is gzip-compressed. - // cloud-init has built-in support for gzip-compressed user data, ignition does not + // cloud-init has built-in support for gzip-compressed user data, ignition does not. // //+optional UncompressedUserData *bool `json:"uncompressedUserData,omitempty"` } -func (c *CloudStackMachine) CompressUserdata() bool { - return c.Spec.UncompressedUserData == nil || !*c.Spec.UncompressedUserData +func (r *CloudStackMachine) CompressUserdata() bool { + return r.Spec.UncompressedUserData == nil || !*r.Spec.UncompressedUserData } type CloudStackResourceIdentifier struct { @@ -102,7 +102,7 @@ type CloudStackResourceIdentifier struct { //+optional ID string `json:"id,omitempty"` - // Cloudstack resource Name + // Cloudstack resource Name. //+optional Name string `json:"name,omitempty"` } @@ -114,11 +114,11 @@ type CloudStackResourceDiskOffering struct { CustomSize int64 `json:"customSizeInGB"` // mount point the data disk uses to mount. The actual partition, mkfs and mount are done by cloud-init generated by kubeadmConfig. MountPath string `json:"mountPath"` - // device name of data disk, for example /dev/vdb + // device name of data disk, for example /dev/vdb. Device string `json:"device"` - // filesystem used by data disk, for example, ext4, xfs + // filesystem used by data disk, for example, ext4, xfs. Filesystem string `json:"filesystem"` - // label of data disk, used by mkfs as label parameter + // label of data disk, used by mkfs as label parameter. Label string `json:"label"` } @@ -143,7 +143,7 @@ type CloudStackMachineStatus struct { //+optional Status *string `json:"status,omitempty"` - // Reason indicates the reason of status failure + // Reason indicates the reason of status failure. //+optional Reason *string `json:"reason,omitempty"` } @@ -154,6 +154,7 @@ func (s *CloudStackMachineStatus) TimeSinceLastStateChange() time.Duration { if s.InstanceStateLastUpdated.IsZero() { return time.Duration(-1) } + return time.Since(s.InstanceStateLastUpdated.Time) } @@ -167,7 +168,7 @@ func (s *CloudStackMachineStatus) TimeSinceLastStateChange() time.Duration { // +kubebuilder:printcolumn:name="ProviderID",type="string",JSONPath=".spec.providerID",description="CloudStack instance ID" // +kubebuilder:printcolumn:name="Machine",type="string",JSONPath=".metadata.ownerReferences[?(@.kind==\"Machine\")].name",description="Machine object which owns with this CloudStackMachine" -// CloudStackMachine is the Schema for the cloudstackmachines API +// CloudStackMachine is the Schema for the cloudstackmachines API. type CloudStackMachine struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -178,7 +179,7 @@ type CloudStackMachine struct { //+kubebuilder:object:root=true -// CloudStackMachineList contains a list of CloudStackMachine +// CloudStackMachineList contains a list of CloudStackMachine. type CloudStackMachineList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta3/cloudstackmachine_webhook.go b/api/v1beta3/cloudstackmachine_webhook.go index 683d4d1a..bcbffb96 100644 --- a/api/v1beta3/cloudstackmachine_webhook.go +++ b/api/v1beta3/cloudstackmachine_webhook.go @@ -23,11 +23,12 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" - "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/webhookutil" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/webhookutil" ) // log is for logging in this package. @@ -47,13 +48,13 @@ var ( _ webhook.Validator = &CloudStackMachine{} ) -// Default implements webhook.Defaulter so a webhook will be registered for the type +// Default implements webhook.Defaulter so a webhook will be registered for the type. func (r *CloudStackMachine) Default() { cloudstackmachinelog.V(1).Info("entered api default setting webhook, no defaults to set", "api resource name", r.Name) // No defaulted values supported yet. } -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. func (r *CloudStackMachine) ValidateCreate() (admission.Warnings, error) { cloudstackmachinelog.V(1).Info("entered validate create webhook", "api resource name", r.Name) @@ -61,14 +62,14 @@ func (r *CloudStackMachine) ValidateCreate() (admission.Warnings, error) { errorList = webhookutil.EnsureAtLeastOneFieldExists(r.Spec.Offering.ID, r.Spec.Offering.Name, "Offering", errorList) errorList = webhookutil.EnsureAtLeastOneFieldExists(r.Spec.Template.ID, r.Spec.Template.Name, "Template", errorList) - if r.Spec.DiskOffering != nil && (len(r.Spec.DiskOffering.ID) > 0 || len(r.Spec.DiskOffering.Name) > 0) { + if r.Spec.DiskOffering != nil && (r.Spec.DiskOffering.ID != "" || r.Spec.DiskOffering.Name != "") { errorList = webhookutil.EnsureIntFieldsAreNotNegative(r.Spec.DiskOffering.CustomSize, "customSizeInGB", errorList) } return nil, webhookutil.AggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, errorList) } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. func (r *CloudStackMachine) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { cloudstackmachinelog.V(1).Info("entered validate update webhook", "api resource name", r.Name) @@ -94,7 +95,7 @@ func (r *CloudStackMachine) ValidateUpdate(old runtime.Object) (admission.Warnin errorList = webhookutil.EnsureEqualStrings(r.Spec.SSHKey, oldSpec.SSHKey, "sshkey", errorList) errorList = webhookutil.EnsureEqualStrings(r.Spec.Template.ID, oldSpec.Template.ID, "template", errorList) errorList = webhookutil.EnsureEqualStrings(r.Spec.Template.Name, oldSpec.Template.Name, "template", errorList) - errorList = webhookutil.EnsureEqualMapStringString(&r.Spec.Details, &oldSpec.Details, "details", errorList) + errorList = webhookutil.EnsureEqualMapStringString(r.Spec.Details, oldSpec.Details, "details", errorList) errorList = webhookutil.EnsureEqualStrings(r.Spec.Affinity, oldSpec.Affinity, "affinity", errorList) if !reflect.DeepEqual(r.Spec.AffinityGroupIDs, oldSpec.AffinityGroupIDs) { // Equivalent to other Ensure funcs. @@ -104,7 +105,7 @@ func (r *CloudStackMachine) ValidateUpdate(old runtime.Object) (admission.Warnin return nil, webhookutil.AggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, errorList) } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type. func (r *CloudStackMachine) ValidateDelete() (admission.Warnings, error) { cloudstackmachinelog.V(1).Info("entered validate delete webhook", "api resource name", r.Name) // No deletion validations. Deletion webhook not enabled. diff --git a/api/v1beta3/cloudstackmachine_webhook_test.go b/api/v1beta3/cloudstackmachine_webhook_test.go index a6de6069..d5de39d9 100644 --- a/api/v1beta3/cloudstackmachine_webhook_test.go +++ b/api/v1beta3/cloudstackmachine_webhook_test.go @@ -19,11 +19,11 @@ package v1beta3_test import ( "context" - infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" - dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" + dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" ) var _ = Describe("CloudStackMachine webhook", func() { diff --git a/api/v1beta3/cloudstackmachinestatechecker_types.go b/api/v1beta3/cloudstackmachinestatechecker_types.go index b47641c8..dcc46ddd 100644 --- a/api/v1beta3/cloudstackmachinestatechecker_types.go +++ b/api/v1beta3/cloudstackmachinestatechecker_types.go @@ -18,13 +18,13 @@ package v1beta3 import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -// CloudStackMachineStateCheckerSpec +// CloudStackMachineStateCheckerSpec defines the desired state of CloudStackMachineStateChecker. type CloudStackMachineStateCheckerSpec struct { // CloudStack machine instance ID InstanceID string `json:"instanceID,omitempty"` } -// CloudStackMachineStateCheckerStatus defines the observed state of CloudStackMachineStateChecker +// CloudStackMachineStateCheckerStatus defines the observed state of CloudStackMachineStateChecker. type CloudStackMachineStateCheckerStatus struct { // Reflects the readiness of the Machine State Checker. //+optional @@ -35,7 +35,7 @@ type CloudStackMachineStateCheckerStatus struct { //+kubebuilder:subresource:status //+kubebuilder:storageversion -// CloudStackMachineStateChecker is the Schema for the cloudstackmachinestatecheckers API +// CloudStackMachineStateChecker is the Schema for the cloudstackmachinestatecheckers API. type CloudStackMachineStateChecker struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -46,7 +46,7 @@ type CloudStackMachineStateChecker struct { //+kubebuilder:object:root=true -// CloudStackMachineStateCheckerList contains a list of CloudStackMachineStateChecker +// CloudStackMachineStateCheckerList contains a list of CloudStackMachineStateChecker. type CloudStackMachineStateCheckerList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta3/cloudstackmachinetemplate_types.go b/api/v1beta3/cloudstackmachinetemplate_types.go index 25e76319..8e033810 100644 --- a/api/v1beta3/cloudstackmachinetemplate_types.go +++ b/api/v1beta3/cloudstackmachinetemplate_types.go @@ -21,7 +21,7 @@ import ( clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ) -// CloudStackMachineTemplateResource defines the data needed to create a CloudstackMachine from a template +// CloudStackMachineTemplateResource defines the data needed to create a CloudstackMachine from a template. type CloudStackMachineTemplateResource struct { // Standard object's metadata. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata @@ -32,7 +32,7 @@ type CloudStackMachineTemplateResource struct { Spec CloudStackMachineSpec `json:"spec"` } -// CloudStackMachineTemplateSpec defines the desired state of CloudstackMachineTemplate +// CloudStackMachineTemplateSpec defines the desired state of CloudstackMachineTemplate. type CloudStackMachineTemplateSpec struct { Template CloudStackMachineTemplateResource `json:"template"` } @@ -41,7 +41,7 @@ type CloudStackMachineTemplateSpec struct { //+kubebuilder:subresource:status //+kubebuilder:storageversion -// CloudStackMachineTemplate is the Schema for the cloudstackmachinetemplates API +// CloudStackMachineTemplate is the Schema for the cloudstackmachinetemplates API. type CloudStackMachineTemplate struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -51,7 +51,7 @@ type CloudStackMachineTemplate struct { //+kubebuilder:object:root=true -// CloudStackMachineTemplateList contains a list of CloudStackMachineTemplate +// CloudStackMachineTemplateList contains a list of CloudStackMachineTemplate. type CloudStackMachineTemplateList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1beta3/cloudstackmachinetemplate_webhook.go b/api/v1beta3/cloudstackmachinetemplate_webhook.go index 33b8eab1..a00b3210 100644 --- a/api/v1beta3/cloudstackmachinetemplate_webhook.go +++ b/api/v1beta3/cloudstackmachinetemplate_webhook.go @@ -24,11 +24,12 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" - "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/webhookutil" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/webhookutil" ) // log is for logging in this package. @@ -48,19 +49,19 @@ var ( _ webhook.Validator = &CloudStackMachineTemplate{} ) -// Default implements webhook.Defaulter so a webhook will be registered for the type +// Default implements webhook.Defaulter so a webhook will be registered for the type. func (r *CloudStackMachineTemplate) Default() { cloudstackmachinetemplatelog.V(1).Info("entered default setting webhook", "api resource name", r.Name) // No defaulted values supported yet. } -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. func (r *CloudStackMachineTemplate) ValidateCreate() (admission.Warnings, error) { cloudstackmachinetemplatelog.V(1).Info("entered validate create webhook", "api resource name", r.Name) var errorList field.ErrorList - // CloudStackMachineTemplateSpec.CloudStackMachineSpec + // CloudStackMachineTemplateSpec.CloudStackMachineSpec. spec := r.Spec.Template.Spec affinity := strings.ToLower(spec.Affinity) @@ -79,7 +80,7 @@ func (r *CloudStackMachineTemplate) ValidateCreate() (admission.Warnings, error) return nil, webhookutil.AggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, errorList) } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. func (r *CloudStackMachineTemplate) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { cloudstackmachinetemplatelog.V(1).Info("entered validate update webhook", "api resource name", r.Name) @@ -88,7 +89,7 @@ func (r *CloudStackMachineTemplate) ValidateUpdate(old runtime.Object) (admissio return nil, errors.NewBadRequest(fmt.Sprintf("expected a CloudStackMachineTemplate but got a %T", old)) } - // CloudStackMachineTemplateSpec.CloudStackMachineTemplateResource.CloudStackMachineSpec + // CloudStackMachineTemplateSpec.CloudStackMachineTemplateResource.CloudStackMachineSpec. spec := r.Spec.Template.Spec oldSpec := oldMachineTemplate.Spec.Template.Spec @@ -102,7 +103,7 @@ func (r *CloudStackMachineTemplate) ValidateUpdate(old runtime.Object) (admissio errorList = webhookutil.EnsureEqualStrings(spec.SSHKey, oldSpec.SSHKey, "sshkey", errorList) errorList = webhookutil.EnsureEqualStrings(spec.Template.ID, oldSpec.Template.ID, "template", errorList) errorList = webhookutil.EnsureEqualStrings(spec.Template.Name, oldSpec.Template.Name, "template", errorList) - errorList = webhookutil.EnsureEqualMapStringString(&spec.Details, &oldSpec.Details, "details", errorList) + errorList = webhookutil.EnsureEqualMapStringString(spec.Details, oldSpec.Details, "details", errorList) errorList = webhookutil.EnsureEqualStrings(spec.Affinity, oldSpec.Affinity, "affinity", errorList) if !reflect.DeepEqual(spec.AffinityGroupIDs, oldSpec.AffinityGroupIDs) { // Equivalent to other Ensure funcs. @@ -112,7 +113,7 @@ func (r *CloudStackMachineTemplate) ValidateUpdate(old runtime.Object) (admissio return nil, webhookutil.AggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, errorList) } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type. func (r *CloudStackMachineTemplate) ValidateDelete() (admission.Warnings, error) { cloudstackmachinetemplatelog.V(1).Info("entered validate delete webhook", "api resource name", r.Name) // No deletion validations. Deletion webhook not enabled. diff --git a/api/v1beta3/cloudstackmachinetemplate_webhook_test.go b/api/v1beta3/cloudstackmachinetemplate_webhook_test.go index a5b5f679..0a84e5b2 100644 --- a/api/v1beta3/cloudstackmachinetemplate_webhook_test.go +++ b/api/v1beta3/cloudstackmachinetemplate_webhook_test.go @@ -19,11 +19,11 @@ package v1beta3_test import ( "context" - infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" - dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" + dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" ) var _ = Describe("CloudStackMachineTemplate webhook", func() { @@ -75,7 +75,8 @@ var _ = Describe("CloudStackMachineTemplate webhook", func() { It("should reject VM disk offering updates to the CloudStackMachineTemplate", func() { dummies.CSMachineTemplate1.Spec.Template.Spec.DiskOffering = &infrav1.CloudStackResourceDiskOffering{ - CloudStackResourceIdentifier: infrav1.CloudStackResourceIdentifier{Name: "DiskOffering2"}} + CloudStackResourceIdentifier: infrav1.CloudStackResourceIdentifier{Name: "DiskOffering2"}, + } Ω(k8sClient.Update(ctx, dummies.CSMachineTemplate1)). Should(MatchError(MatchRegexp(forbiddenRegex, "diskOffering"))) }) diff --git a/api/v1beta3/doc.go b/api/v1beta3/doc.go index 9613646b..d5717612 100644 --- a/api/v1beta3/doc.go +++ b/api/v1beta3/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package v1beta3 contains API Schema definitions for the infrastructure v1beta3 API group +// Package v1beta3 contains API Schema definitions for the infrastructure v1beta3 API group. // +kubebuilder:object:generate=true // +groupName=infrastructure.cluster.x-k8s.io package v1beta3 diff --git a/api/v1beta3/groupversion_info.go b/api/v1beta3/groupversion_info.go index 3127b4a2..277c8cbb 100644 --- a/api/v1beta3/groupversion_info.go +++ b/api/v1beta3/groupversion_info.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package v1beta3 contains API Schema definitions for the infrastructure v1beta3 API group +// Package v1beta3 contains API Schema definitions for the infrastructure v1beta3 API group. // +kubebuilder:object:generate=true // +groupName=infrastructure.cluster.x-k8s.io package v1beta3 @@ -25,10 +25,10 @@ import ( ) var ( - // GroupVersion is group version used to register these objects + // GroupVersion is group version used to register these objects. GroupVersion = schema.GroupVersion{Group: "infrastructure.cluster.x-k8s.io", Version: "v1beta3"} - // SchemeBuilder is used to add go types to the GroupVersionKind scheme + // SchemeBuilder is used to add go types to the GroupVersionKind scheme. SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} // AddToScheme adds the types in this group-version to the given scheme. diff --git a/api/v1beta3/webhook_suite_test.go b/api/v1beta3/webhook_suite_test.go index 0ca9bb49..62335b14 100644 --- a/api/v1beta3/webhook_suite_test.go +++ b/api/v1beta3/webhook_suite_test.go @@ -25,27 +25,30 @@ import ( "testing" "time" - "k8s.io/client-go/rest" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - - admissionv1 "k8s.io/api/admission/v1" //+kubebuilder:scaffold:imports - "k8s.io/apimachinery/pkg/runtime" - infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" + admissionv1 "k8s.io/api/admission/v1" + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/webhook" + + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) // These tests use Ginkgo (BDD-style Go testing framework). Refer to // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. -var k8sClient client.Client -var testEnv *envtest.Environment -var ctx context.Context -var cancel context.CancelFunc +var ( + cfg *rest.Config + k8sClient client.Client + testEnv *envtest.Environment + ctx context.Context + cancel context.CancelFunc +) func TestAPIs(t *testing.T) { RegisterFailHandler(Fail) @@ -58,26 +61,20 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("../../", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, ErrorIfCRDPathMissing: false, WebhookInstallOptions: envtest.WebhookInstallOptions{ - Paths: []string{filepath.Join("../../", "config", "webhook")}, + Paths: []string{filepath.Join("..", "..", "config", "webhook")}, }, } - var cfg *rest.Config var err error - done := make(chan interface{}) - go func() { - defer GinkgoRecover() - cfg, err = testEnv.Start() - close(done) - }() - Eventually(done).WithTimeout(time.Minute).Should(BeClosed()) + // cfg is defined in this file globally. + cfg, err = testEnv.Start() Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) - scheme := runtime.NewScheme() + scheme := apimachineryruntime.NewScheme() err = infrav1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) @@ -90,13 +87,15 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) - // start webhook server using Manager + // start webhook server using Manager. webhookInstallOptions := &testEnv.WebhookInstallOptions mgr, err := ctrl.NewManager(cfg, ctrl.Options{ - Scheme: scheme, - Host: webhookInstallOptions.LocalServingHost, - Port: webhookInstallOptions.LocalServingPort, - CertDir: webhookInstallOptions.LocalServingCertDir, + Scheme: scheme, + WebhookServer: webhook.NewServer(webhook.Options{ + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, + }), LeaderElection: false, MetricsBindAddress: "0", }) @@ -110,27 +109,26 @@ var _ = BeforeSuite(func() { go func() { defer GinkgoRecover() - err = mgr.Start(ctrl.SetupSignalHandler()) + err = mgr.Start(ctx) Expect(err).NotTo(HaveOccurred()) }() - // wait for the webhook server to get ready + // wait for the webhook server to get ready. dialer := &net.Dialer{Timeout: time.Second} addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) Eventually(func() error { - conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) //nolint:gosec //Used for testing only + conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) if err != nil { return err } - conn.Close() - return nil - }).Should(Succeed()) + return conn.Close() + }).Should(Succeed()) }) var _ = AfterSuite(func() { - cancel() By("tearing down the test environment") + cancel() err := testEnv.Stop() Expect(err).NotTo(HaveOccurred()) }) diff --git a/controllers/cloudstackaffinitygroup_controller.go b/controllers/cloudstackaffinitygroup_controller.go index f1e880d2..6e000d35 100644 --- a/controllers/cloudstackaffinitygroup_controller.go +++ b/controllers/cloudstackaffinitygroup_controller.go @@ -54,6 +54,7 @@ func NewCSAGReconciliationRunner() *CloudStackAGReconciliationRunner { r.FailureDomain = &infrav1.CloudStackFailureDomain{} // Setup the base runner. Initializes pointers and links reconciliation methods. r.ReconciliationRunner = csCtrlrUtils.NewRunner(r, r.ReconciliationSubject, "CloudStackAffinityGroup") + return r } @@ -63,6 +64,7 @@ func (reconciler *CloudStackAffinityGroupReconciler) Reconcile(ctx context.Conte r.WithAdditionalCommonStages( r.GetFailureDomainByName(func() string { return r.ReconciliationSubject.Spec.FailureDomainName }, r.FailureDomain), r.AsFailureDomainUser(&r.FailureDomain.Spec)) + return r.RunBaseReconciliationStages() } @@ -74,6 +76,7 @@ func (r *CloudStackAGReconciliationRunner) Reconcile() (ctrl.Result, error) { } r.ReconciliationSubject.Spec.ID = affinityGroup.ID r.ReconciliationSubject.Status.Ready = true + return ctrl.Result{}, nil } @@ -86,12 +89,14 @@ func (r *CloudStackAGReconciliationRunner) ReconcileDelete() (ctrl.Result, error // deleting the affinity group. Removing finalizer if affinity group is not // present on Cloudstack ensures affinity group object deletion is not blocked. controllerutil.RemoveFinalizer(r.ReconciliationSubject, infrav1.AffinityGroupFinalizer) + return ctrl.Result{}, nil } if err := r.CSUser.DeleteAffinityGroup(group); err != nil { return ctrl.Result{}, err } controllerutil.RemoveFinalizer(r.ReconciliationSubject, infrav1.AffinityGroupFinalizer) + return ctrl.Result{}, nil } diff --git a/controllers/cloudstackaffinitygroup_controller_test.go b/controllers/cloudstackaffinitygroup_controller_test.go index 9f255eb5..ebeecdfc 100644 --- a/controllers/cloudstackaffinitygroup_controller_test.go +++ b/controllers/cloudstackaffinitygroup_controller_test.go @@ -20,10 +20,11 @@ import ( "github.com/golang/mock/gomock" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" - dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" + + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" + dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" ) var _ = Describe("CloudStackAffinityGroupReconciler", func() { @@ -37,7 +38,7 @@ var _ = Describe("CloudStackAffinityGroupReconciler", func() { // Modify failure domain name the same way the cluster controller would. dummies.CSAffinityGroup.Spec.FailureDomainName = dummies.CSFailureDomain1.Spec.Name - Ω(k8sClient.Create(ctx, dummies.CSFailureDomain1)) + Ω(k8sClient.Create(ctx, dummies.CSFailureDomain1)).Should(Succeed()) Ω(k8sClient.Create(ctx, dummies.CSAffinityGroup)).Should(Succeed()) mockCloudClient.EXPECT().GetOrCreateAffinityGroup(gomock.Any()).AnyTimes() @@ -51,6 +52,7 @@ var _ = Describe("CloudStackAffinityGroupReconciler", func() { return affinityGroups.Items[0].Status.Ready } } + return false }, timeout).WithPolling(pollInterval).Should(BeTrue()) }) diff --git a/controllers/cloudstackcluster_controller.go b/controllers/cloudstackcluster_controller.go index 10ea68ac..d4bf5e5f 100644 --- a/controllers/cloudstackcluster_controller.go +++ b/controllers/cloudstackcluster_controller.go @@ -22,6 +22,9 @@ import ( "reflect" "github.com/pkg/errors" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + "sigs.k8s.io/cluster-api/util" + "sigs.k8s.io/cluster-api/util/predicates" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -32,9 +35,6 @@ import ( infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" csCtrlrUtils "sigs.k8s.io/cluster-api-provider-cloudstack/controllers/utils" - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" - "sigs.k8s.io/cluster-api/util" - "sigs.k8s.io/cluster-api/util/predicates" ) // RBAC permissions used in all reconcilers. Events and Secrets. @@ -78,7 +78,7 @@ func NewCSClusterReconciliationRunner() *CloudStackClusterReconciliationRunner { } // Reconcile is the method k8s will call upon a reconciliation request. -func (reconciler *CloudStackClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (retRes ctrl.Result, retErr error) { +func (reconciler *CloudStackClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { return NewCSClusterReconciliationRunner(). UsingBaseReconciler(reconciler.ReconcilerBase). ForRequest(req). @@ -87,7 +87,7 @@ func (reconciler *CloudStackClusterReconciler) Reconcile(ctx context.Context, re } // Reconcile actually reconciles the CloudStackCluster. -func (r *CloudStackClusterReconciliationRunner) Reconcile() (res ctrl.Result, reterr error) { +func (r *CloudStackClusterReconciliationRunner) Reconcile() (ctrl.Result, error) { return r.RunReconciliationStages( r.SetFailureDomainsStatusMap, r.CreateFailureDomains(r.ReconciliationSubject.Spec.FailureDomains), @@ -101,6 +101,7 @@ func (r *CloudStackClusterReconciliationRunner) Reconcile() (res ctrl.Result, re func (r *CloudStackClusterReconciliationRunner) SetReady() (ctrl.Result, error) { controllerutil.AddFinalizer(r.ReconciliationSubject, infrav1.ClusterFinalizer) r.ReconciliationSubject.Status.Ready = true + return ctrl.Result{}, nil } @@ -115,6 +116,7 @@ func (r *CloudStackClusterReconciliationRunner) VerifyFailureDomainCRDs() (ctrl. if !fd.Status.Ready { return r.RequeueWithMessage(fmt.Sprintf("Required FailureDomain %s not ready, requeueing.", fd.Spec.Name)) } + break } } @@ -122,6 +124,7 @@ func (r *CloudStackClusterReconciliationRunner) VerifyFailureDomainCRDs() (ctrl. return r.RequeueWithMessage(fmt.Sprintf("Required FailureDomain %s not found, requeueing.", requiredFdSpec.Name)) } } + return ctrl.Result{}, nil } @@ -134,6 +137,7 @@ func (r *CloudStackClusterReconciliationRunner) SetFailureDomainsStatusMap() (ct ControlPlane: true, Attributes: map[string]string{"MetaHashName": metaHashName}, } } + return ctrl.Result{}, nil } @@ -149,9 +153,11 @@ func (r *CloudStackClusterReconciliationRunner) ReconcileDelete() (ctrl.Result, return ctrl.Result{}, err } } + return r.RequeueWithMessage("Child FailureDomains still present, requeueing.") } controllerutil.RemoveFinalizer(r.ReconciliationSubject, infrav1.ClusterFinalizer) + return ctrl.Result{}, nil } @@ -195,5 +201,6 @@ func (reconciler *CloudStackClusterReconciler) SetupWithManager(ctx context.Cont if err != nil { return errors.Wrap(err, "failed setting up with a controller manager") } + return nil } diff --git a/controllers/cloudstackcluster_controller_test.go b/controllers/cloudstackcluster_controller_test.go index aeeaecbe..cc1ef8bd 100644 --- a/controllers/cloudstackcluster_controller_test.go +++ b/controllers/cloudstackcluster_controller_test.go @@ -20,11 +20,12 @@ import ( "github.com/golang/mock/gomock" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" "sigs.k8s.io/cluster-api-provider-cloudstack/controllers" dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller" ) var _ = Describe("CloudStackClusterReconciler", func() { @@ -44,6 +45,7 @@ var _ = Describe("CloudStackClusterReconciler", func() { if err := k8sClient.Get(ctx, key, tempfd); err != nil { return true } + return false }, timeout).WithPolling(pollInterval).Should(BeTrue()) }) diff --git a/controllers/cloudstackfailuredomain_controller.go b/controllers/cloudstackfailuredomain_controller.go index 519abfc4..60c44512 100644 --- a/controllers/cloudstackfailuredomain_controller.go +++ b/controllers/cloudstackfailuredomain_controller.go @@ -18,7 +18,6 @@ package controllers import ( "context" - "sigs.k8s.io/controller-runtime/pkg/controller" "sort" "github.com/pkg/errors" @@ -28,6 +27,7 @@ import ( "sigs.k8s.io/cluster-api/util/predicates" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" @@ -76,7 +76,7 @@ func NewCSFailureDomainReconciliationRunner() *CloudStackFailureDomainReconcilia } // Reconcile is the method k8s will call upon a reconciliation request. -func (reconciler *CloudStackFailureDomainReconciler) Reconcile(ctx context.Context, req ctrl.Request) (retRes ctrl.Result, retErr error) { +func (reconciler *CloudStackFailureDomainReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { return NewCSFailureDomainReconciliationRunner(). UsingBaseReconciler(reconciler.ReconcilerBase). ForRequest(req). @@ -85,7 +85,7 @@ func (reconciler *CloudStackFailureDomainReconciler) Reconcile(ctx context.Conte } // Reconcile on the ReconciliationRunner actually attempts to modify or create the reconciliation subject. -func (r *CloudStackFailureDomainReconciliationRunner) Reconcile() (retRes ctrl.Result, retErr error) { +func (r *CloudStackFailureDomainReconciliationRunner) Reconcile() (ctrl.Result, error) { res, err := r.AsFailureDomainUser(&r.ReconciliationSubject.Spec)() if r.ShouldReturn(res, err) { return res, err @@ -120,6 +120,7 @@ func (r *CloudStackFailureDomainReconciliationRunner) Reconcile() (retRes ctrl.R } } r.ReconciliationSubject.Status.Ready = true + return ctrl.Result{}, nil } @@ -153,6 +154,7 @@ func (r *CloudStackFailureDomainReconciliationRunner) GetAllMachinesInFailureDom return items[i].Name < items[j].Name }) r.Machines = items + return ctrl.Result{}, nil } @@ -168,46 +170,49 @@ func (r *CloudStackFailureDomainReconciliationRunner) RequeueIfClusterNotReady() } } } + return ctrl.Result{}, nil } // RequeueIfMachineCannotBeRemoved checks for each machine to confirm it is not risky to remove it. -func (r *CloudStackFailureDomainReconciliationRunner) RequeueIfMachineCannotBeRemoved() (ctrl.Result, error) { +func (r *CloudStackFailureDomainReconciliationRunner) RequeueIfMachineCannotBeRemoved() (ctrl.Result, error) { //nolint:gocognit // check CAPI machines for CloudStack machines found. for _, machine := range r.Machines { for _, ref := range machine.OwnerReferences { - if ref.Kind != "Machine" { - owner := &unstructured.Unstructured{} - owner.SetGroupVersionKind(schema.FromAPIVersionAndKind(ref.APIVersion, ref.Kind)) - if err := r.K8sClient.Get(r.RequestCtx, client.ObjectKey{Namespace: machine.Namespace, Name: ref.Name}, owner); err != nil { - return ctrl.Result{}, err - } - specReplicas, statusReplicas, err := getSpecAndStatusReplicas(owner, ref.Name, machine.Name) - if err != nil { - return ctrl.Result{}, err - } - if specReplicas != statusReplicas { - return r.RequeueWithMessage("spec.replicas <> status.replicas, ", "owner", ref.Name, "spec.replicas", specReplicas, "status.replicas", statusReplicas) - } - - statusReady, found, err := unstructured.NestedBool(owner.Object, "status", "ready") - if found && err != nil { - return ctrl.Result{}, err - } - if found && !statusReady { - return r.RequeueWithMessage("status.ready not true, ", "owner", ref.Name) - } - - statusReadyReplicas, found, err := unstructured.NestedInt64(owner.Object, "status", "readyReplicas") - if found && err != nil { - return ctrl.Result{}, err - } - if found && statusReadyReplicas != statusReplicas { - return r.RequeueWithMessage("status.replicas <> status.readyReplicas, ", "owner", ref.Name, "status.replicas", statusReplicas, "status.readyReplicas", statusReadyReplicas) - } + if ref.Kind == "Machine" { + continue + } + owner := &unstructured.Unstructured{} + owner.SetGroupVersionKind(schema.FromAPIVersionAndKind(ref.APIVersion, ref.Kind)) + if err := r.K8sClient.Get(r.RequestCtx, client.ObjectKey{Namespace: machine.Namespace, Name: ref.Name}, owner); err != nil { + return ctrl.Result{}, err + } + specReplicas, statusReplicas, err := getSpecAndStatusReplicas(owner, ref.Name, machine.Name) + if err != nil { + return ctrl.Result{}, err + } + if specReplicas != statusReplicas { + return r.RequeueWithMessage("spec.replicas <> status.replicas, ", "owner", ref.Name, "spec.replicas", specReplicas, "status.replicas", statusReplicas) + } + + statusReady, found, err := unstructured.NestedBool(owner.Object, "status", "ready") + if found && err != nil { + return ctrl.Result{}, err + } + if found && !statusReady { + return r.RequeueWithMessage("status.ready not true, ", "owner", ref.Name) + } + + statusReadyReplicas, found, err := unstructured.NestedInt64(owner.Object, "status", "readyReplicas") + if found && err != nil { + return ctrl.Result{}, err + } + if found && statusReadyReplicas != statusReplicas { + return r.RequeueWithMessage("status.replicas <> status.readyReplicas, ", "owner", ref.Name, "status.replicas", statusReplicas, "status.readyReplicas", statusReadyReplicas) } } } + return ctrl.Result{}, nil } @@ -240,27 +245,31 @@ func (r *CloudStackFailureDomainReconciliationRunner) ClearMachines() (ctrl.Resu // pick first machine to delete if len(r.Machines) > 0 { for _, ref := range r.Machines[0].OwnerReferences { - if ref.Kind == "Machine" { - machine := &clusterv1.Machine{} - if err := r.K8sClient.Get(r.RequestCtx, client.ObjectKey{Namespace: r.ReconciliationSubject.Namespace, Name: ref.Name}, machine); err != nil { - return ctrl.Result{}, err - } - if !machine.DeletionTimestamp.IsZero() { - return r.RequeueWithMessage("machine is being deleted, ", "machine", machine.Name) - } - if err := r.K8sClient.Delete(r.RequestCtx, machine); err != nil { - return ctrl.Result{}, err - } - return r.RequeueWithMessage("start to delete machine, ", "machine", machine.Name) + if ref.Kind != "Machine" { + continue } + machine := &clusterv1.Machine{} + if err := r.K8sClient.Get(r.RequestCtx, client.ObjectKey{Namespace: r.ReconciliationSubject.Namespace, Name: ref.Name}, machine); err != nil { + return ctrl.Result{}, err + } + if !machine.DeletionTimestamp.IsZero() { + return r.RequeueWithMessage("machine is being deleted, ", "machine", machine.Name) + } + if err := r.K8sClient.Delete(r.RequestCtx, machine); err != nil { + return ctrl.Result{}, err + } + + return r.RequeueWithMessage("start to delete machine, ", "machine", machine.Name) } } + return ctrl.Result{}, nil } // RemoveFinalizer just removes the finalizer from the failure domain. func (r *CloudStackFailureDomainReconciliationRunner) RemoveFinalizer() (ctrl.Result, error) { controllerutil.RemoveFinalizer(r.ReconciliationSubject, infrav1.FailureDomainFinalizer) + return ctrl.Result{}, nil } diff --git a/controllers/cloudstackfailuredomain_controller_test.go b/controllers/cloudstackfailuredomain_controller_test.go index 3c3b6898..536ca7e7 100644 --- a/controllers/cloudstackfailuredomain_controller_test.go +++ b/controllers/cloudstackfailuredomain_controller_test.go @@ -22,13 +22,14 @@ import ( . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/utils/pointer" - infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" - "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" - dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/util/patch" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" + + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" + dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" ) var _ = Describe("CloudStackFailureDomainReconciler", func() { @@ -40,8 +41,8 @@ var _ = Describe("CloudStackFailureDomainReconciler", func() { // Modify failure domain name the same way the cluster controller would. dummies.CSFailureDomain1.Name = dummies.CSFailureDomain1.Name + "-" + dummies.CSCluster.Name - Ω(k8sClient.Create(ctx, dummies.ACSEndpointSecret1)) - Ω(k8sClient.Create(ctx, dummies.CSFailureDomain1)) + Ω(k8sClient.Create(ctx, dummies.ACSEndpointSecret1)).Should(Succeed()) + Ω(k8sClient.Create(ctx, dummies.CSFailureDomain1)).Should(Succeed()) mockCloudClient.EXPECT().ResolveZone(gomock.Any()).MinTimes(1) @@ -57,7 +58,7 @@ var _ = Describe("CloudStackFailureDomainReconciler", func() { return getFailuredomainStatus(dummies.CSFailureDomain1) }, timeout).WithPolling(pollInterval).Should(BeTrue()) - Ω(k8sClient.Delete(ctx, dummies.CSFailureDomain1)) + Ω(k8sClient.Delete(ctx, dummies.CSFailureDomain1)).Should(Succeed()) tempfd := &infrav1.CloudStackFailureDomain{} Eventually(func() bool { @@ -65,6 +66,7 @@ var _ = Describe("CloudStackFailureDomainReconciler", func() { if err := k8sClient.Get(ctx, key, tempfd); err != nil { return errors.IsNotFound(err) } + return false }, timeout).WithPolling(pollInterval).Should(BeTrue()) }) @@ -90,11 +92,12 @@ var _ = Describe("CloudStackFailureDomainReconciler", func() { Status: "False", }, } + return ph.Patch(ctx, dummies.CAPICluster, patch.WithStatusObservedGeneration{}) }, timeout).Should(Succeed()) } - Ω(k8sClient.Delete(ctx, dummies.CSFailureDomain1)) + Ω(k8sClient.Delete(ctx, dummies.CSFailureDomain1)).Should(Succeed()) CAPIMachine := &clusterv1.Machine{} if shouldDeleteVM { @@ -103,6 +106,7 @@ var _ = Describe("CloudStackFailureDomainReconciler", func() { if err := k8sClient.Get(ctx, key, CAPIMachine); err != nil { return errors.IsNotFound(err) } + return false }, timeout).WithPolling(pollInterval).Should(BeTrue()) } else { @@ -111,10 +115,10 @@ var _ = Describe("CloudStackFailureDomainReconciler", func() { if err := k8sClient.Get(ctx, key, CAPIMachine); err == nil { return CAPIMachine.DeletionTimestamp.IsZero() } + return false }, timeout).WithPolling(pollInterval).Should(BeTrue()) } - }, // should delete - simulate owner is kubeadmcontrolplane Entry("Should delete machine if spec.replicas > 1", true, pointer.Int32(2), pointer.Int32(2), pointer.Int32(2), pointer.Bool(true), true), @@ -138,5 +142,6 @@ func getFailuredomainStatus(failureDomain *infrav1.CloudStackFailureDomain) bool if err := k8sClient.Get(ctx, key, tempfd); err == nil { return tempfd.Status.Ready } + return false } diff --git a/controllers/cloudstackisolatednetwork_controller.go b/controllers/cloudstackisolatednetwork_controller.go index eb97f3bd..8add50c0 100644 --- a/controllers/cloudstackisolatednetwork_controller.go +++ b/controllers/cloudstackisolatednetwork_controller.go @@ -19,19 +19,19 @@ package controllers import ( "context" "reflect" - "sigs.k8s.io/cluster-api/util/annotations" - "sigs.k8s.io/controller-runtime/pkg/builder" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/predicate" "strings" + "github.com/pkg/errors" + "sigs.k8s.io/cluster-api/util/annotations" "sigs.k8s.io/cluster-api/util/patch" "sigs.k8s.io/cluster-api/util/predicates" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" - "github.com/pkg/errors" infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" csCtrlrUtils "sigs.k8s.io/cluster-api-provider-cloudstack/controllers/utils" "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" @@ -41,7 +41,7 @@ import ( //+kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=cloudstackisolatednetworks/status,verbs=get;update;patch //+kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=cloudstackisolatednetworks/finalizers,verbs=update -// CloudStackIsoNetReconciler reconciles a CloudStackZone object +// CloudStackIsoNetReconciler reconciles a CloudStackZone object. type CloudStackIsoNetReconciler struct { csCtrlrUtils.ReconcilerBase } @@ -58,18 +58,20 @@ func NewCSIsoNetReconciliationRunner() *CloudStackIsoNetReconciliationRunner { // Set concrete type and init pointers. r := &CloudStackIsoNetReconciliationRunner{ReconciliationSubject: &infrav1.CloudStackIsolatedNetwork{}} r.FailureDomain = &infrav1.CloudStackFailureDomain{} - // Setup the base runner. Initializes pointers and links reconciliation methods. + // Set up the base runner. Initializes pointers and links reconciliation methods. r.ReconciliationRunner = csCtrlrUtils.NewRunner(r, r.ReconciliationSubject, "CloudStackIsolatedNetwork") + return r } -func (reconciler *CloudStackIsoNetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, retErr error) { +func (reconciler *CloudStackIsoNetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { r := NewCSIsoNetReconciliationRunner() r.UsingBaseReconciler(reconciler.ReconcilerBase).ForRequest(req).WithRequestCtx(ctx) r.WithAdditionalCommonStages( r.GetFailureDomainByName(func() string { return r.ReconciliationSubject.Spec.FailureDomainName }, r.FailureDomain), r.AsFailureDomainUser(&r.FailureDomain.Spec), ) + return r.RunBaseReconciliationStages() } @@ -98,7 +100,7 @@ func (r *CloudStackIsoNetReconciliationRunner) Reconcile() (ctrl.Result, error) if !annotations.IsExternallyManaged(r.CSCluster) { pubIP, err := r.CSUser.AssociatePublicIPAddress(r.FailureDomain, r.ReconciliationSubject, r.CSCluster.Spec.ControlPlaneEndpoint.Host) if err != nil { - return ctrl.Result{}, errors.Wrap(err, "associating public IP address") + return ctrl.Result{}, errors.Wrap(err, "failed to associate public IP address") } r.ReconciliationSubject.Spec.ControlPlaneEndpoint.Host = pubIP.Ipaddress r.CSCluster.Spec.ControlPlaneEndpoint.Host = pubIP.Ipaddress @@ -129,7 +131,7 @@ func (r *CloudStackIsoNetReconciliationRunner) Reconcile() (ctrl.Result, error) return ctrl.Result{}, nil } -func (r *CloudStackIsoNetReconciliationRunner) ReconcileDelete() (retRes ctrl.Result, retErr error) { +func (r *CloudStackIsoNetReconciliationRunner) ReconcileDelete() (ctrl.Result, error) { r.Log.Info("Deleting IsolatedNetwork.") if err := r.CSUser.DisposeIsoNetResources(r.ReconciliationSubject, r.CSCluster); err != nil { if !strings.Contains(strings.ToLower(err.Error()), "no match found") { @@ -137,6 +139,7 @@ func (r *CloudStackIsoNetReconciliationRunner) ReconcileDelete() (retRes ctrl.Re } } controllerutil.RemoveFinalizer(r.ReconciliationSubject, infrav1.IsolatedNetworkFinalizer) + return ctrl.Result{}, nil } diff --git a/controllers/cloudstackisolatednetwork_controller_test.go b/controllers/cloudstackisolatednetwork_controller_test.go index 98bf6feb..f48f2c58 100644 --- a/controllers/cloudstackisolatednetwork_controller_test.go +++ b/controllers/cloudstackisolatednetwork_controller_test.go @@ -22,10 +22,12 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/types" - infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" - dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" + "k8s.io/utils/pointer" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" + dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" ) var _ = Describe("CloudStackIsolatedNetworkReconciler", func() { @@ -59,6 +61,36 @@ var _ = Describe("CloudStackIsolatedNetworkReconciler", func() { return true } } + + return false + }, timeout).WithPolling(pollInterval).Should(BeTrue()) + }) + + It("Should succeed if API load balancer is disabled.", func() { + dummies.CSCluster.Spec.APIServerLoadBalancer.Enabled = pointer.Bool(false) + mockCloudClient.EXPECT().GetOrCreateIsolatedNetwork(g.Any(), g.Any(), g.Any()).AnyTimes() + mockCloudClient.EXPECT().AddClusterTag(g.Any(), g.Any(), g.Any()).AnyTimes() + mockCloudClient.EXPECT().AssociatePublicIPAddress(g.Any(), g.Any(), g.Any()).AnyTimes().Return(&cloudstack.PublicIpAddress{ + Id: dummies.PublicIPID, + Associatednetworkid: dummies.ISONet1.ID, + Ipaddress: dummies.CSCluster.Spec.ControlPlaneEndpoint.Host, + }, nil) + mockCloudClient.EXPECT().ReconcileLoadBalancer(g.Any(), g.Any(), g.Any()).AnyTimes() + + // We use CSFailureDomain2 here because CSFailureDomain1 has an empty Spec.Zone.ID + dummies.CSISONet1.Spec.FailureDomainName = dummies.CSFailureDomain2.Spec.Name + Ω(k8sClient.Create(ctx, dummies.CSFailureDomain2)).Should(Succeed()) + Ω(k8sClient.Create(ctx, dummies.CSISONet1)).Should(Succeed()) + + Eventually(func() bool { + tempIsoNet := &infrav1.CloudStackIsolatedNetwork{} + key := client.ObjectKeyFromObject(dummies.CSISONet1) + if err := k8sClient.Get(ctx, key, tempIsoNet); err == nil { + if tempIsoNet.Status.Ready == true { + return true + } + } + return false }, timeout).WithPolling(pollInterval).Should(BeTrue()) }) diff --git a/controllers/cloudstackmachine_controller.go b/controllers/cloudstackmachine_controller.go index 0aa062bd..e8032e46 100644 --- a/controllers/cloudstackmachine_controller.go +++ b/controllers/cloudstackmachine_controller.go @@ -25,13 +25,13 @@ import ( "strings" "time" - "k8s.io/utils/pointer" - "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/util" "sigs.k8s.io/cluster-api/util/annotations" "sigs.k8s.io/cluster-api/util/predicates" @@ -47,7 +47,6 @@ import ( infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" "sigs.k8s.io/cluster-api-provider-cloudstack/controllers/utils" "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ) var ( @@ -87,7 +86,7 @@ type CloudStackMachineReconciliationRunner struct { AffinityGroup *infrav1.CloudStackAffinityGroup } -// CloudStackMachineReconciler reconciles a CloudStackMachine object +// CloudStackMachineReconciler reconciles a CloudStackMachine object.. type CloudStackMachineReconciler struct { utils.ReconcilerBase } @@ -101,14 +100,15 @@ func NewCSMachineReconciliationRunner() *CloudStackMachineReconciliationRunner { r.IsoNet = &infrav1.CloudStackIsolatedNetwork{} r.AffinityGroup = &infrav1.CloudStackAffinityGroup{} r.FailureDomain = &infrav1.CloudStackFailureDomain{} - // Setup the base runner. Initializes pointers and links reconciliation methods. + // Set up the base runner. Initializes pointers and links reconciliation methods. r.ReconciliationRunner = utils.NewRunner(r, r.ReconciliationSubject, "CloudStackMachine") + return r } // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -func (reconciler *CloudStackMachineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, retErr error) { +func (reconciler *CloudStackMachineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { r := NewCSMachineReconciliationRunner() r.UsingBaseReconciler(reconciler.ReconcilerBase).ForRequest(req).WithRequestCtx(ctx) r.WithAdditionalCommonStages( @@ -117,12 +117,13 @@ func (reconciler *CloudStackMachineReconciler) Reconcile(ctx context.Context, re r.SetFailureDomainOnCSMachine, r.GetFailureDomainByName(func() string { return r.ReconciliationSubject.Spec.FailureDomainName }, r.FailureDomain), r.AsFailureDomainUser(&r.FailureDomain.Spec)) - res, retErr = r.RunBaseReconciliationStages() + res, err := r.RunBaseReconciliationStages() r.Log.V(1).Info("Reconciliation finished.") - return res, retErr + + return res, err } -func (r *CloudStackMachineReconciliationRunner) Reconcile() (retRes ctrl.Result, reterr error) { +func (r *CloudStackMachineReconciliationRunner) Reconcile() (ctrl.Result, error) { return r.RunReconciliationStages( r.DeleteMachineIfFailuredomainNotExist, r.GetObjectByName("placeholder", r.IsoNet, @@ -140,7 +141,7 @@ func (r *CloudStackMachineReconciliationRunner) Reconcile() (retRes ctrl.Result, // ConsiderAffinity sets machine affinity if needed. It also creates or gets an affinity group resource if required and // checks it for readiness. func (r *CloudStackMachineReconciliationRunner) ConsiderAffinity() (ctrl.Result, error) { - if r.ReconciliationSubject.Spec.Affinity == infrav1.NoAffinity || + if r.ReconciliationSubject.Spec.Affinity == infrav1.AffinityTypeNo || r.ReconciliationSubject.Spec.Affinity == "" { // No managed affinity. return ctrl.Result{}, nil } @@ -178,7 +179,7 @@ func (r *CloudStackMachineReconciliationRunner) ConsiderAffinity() (ctrl.Result, } // SetFailureDomainOnCSMachine sets the failure domain the machine should launch in. -func (r *CloudStackMachineReconciliationRunner) SetFailureDomainOnCSMachine() (retRes ctrl.Result, reterr error) { +func (r *CloudStackMachineReconciliationRunner) SetFailureDomainOnCSMachine() (ctrl.Result, error) { if r.ReconciliationSubject.Spec.FailureDomainName == "" { var name string // CAPIMachine is null if it's been deleted but we're still reconciling the CS machine. @@ -195,11 +196,12 @@ func (r *CloudStackMachineReconciliationRunner) SetFailureDomainOnCSMachine() (r r.ReconciliationSubject.Spec.FailureDomainName = name r.ReconciliationSubject.Labels[infrav1.FailureDomainLabelName] = infrav1.FailureDomainHashedMetaName(name, r.CAPICluster.Name) } + return ctrl.Result{}, nil } // DeleteMachineIfFailuredomainNotExist delete CAPI machine if machine is deployed in a failuredomain that does not exist anymore. -func (r *CloudStackMachineReconciliationRunner) DeleteMachineIfFailuredomainNotExist() (retRes ctrl.Result, reterr error) { +func (r *CloudStackMachineReconciliationRunner) DeleteMachineIfFailuredomainNotExist() (ctrl.Result, error) { if r.CAPIMachine.Spec.FailureDomain == nil { return ctrl.Result{}, nil } @@ -208,6 +210,7 @@ func (r *CloudStackMachineReconciliationRunner) DeleteMachineIfFailuredomainNotE for _, fd := range r.CSCluster.Spec.FailureDomains { if capiAssignedFailuredomainName == fd.Name { exist = true + break } } @@ -223,9 +226,10 @@ func (r *CloudStackMachineReconciliationRunner) DeleteMachineIfFailuredomainNotE // GetOrCreateVMInstance gets or creates a VM instance. // Implicitly it also fetches its bootstrap secret in order to create said instance. -func (r *CloudStackMachineReconciliationRunner) GetOrCreateVMInstance() (retRes ctrl.Result, reterr error) { +func (r *CloudStackMachineReconciliationRunner) GetOrCreateVMInstance() (ctrl.Result, error) { if r.CAPIMachine.Spec.Bootstrap.DataSecretName == nil { r.Recorder.Event(r.ReconciliationSubject, "Normal", "Creating", BootstrapDataNotReady) + return r.RequeueWithMessage(BootstrapDataNotReady + ".") } @@ -263,35 +267,40 @@ func processCustomMetadata(data []byte, r *CloudStackMachineReconciliationRunner // {{ ds.meta_data.hostname }} is expected to be used as a node name when kubelet register a node userData := hostnameMatcher.ReplaceAllString(string(data), r.CAPIMachine.Name) userData = failuredomainMatcher.ReplaceAllString(userData, r.FailureDomain.Spec.Name) + return userData } // RequeueIfInstanceNotRunning checks the Instance's status for running state and requeues otherwise. -func (r *CloudStackMachineReconciliationRunner) RequeueIfInstanceNotRunning() (retRes ctrl.Result, reterr error) { - if r.ReconciliationSubject.Status.InstanceState == "Running" { +func (r *CloudStackMachineReconciliationRunner) RequeueIfInstanceNotRunning() (ctrl.Result, error) { + switch r.ReconciliationSubject.Status.InstanceState { + case cloud.VMStateRunning: // Only emit this event when relevant. if !r.ReconciliationSubject.Status.Ready { - r.Recorder.Event(r.ReconciliationSubject, "Normal", "Running", MachineInstanceRunning) + r.Recorder.Event(r.ReconciliationSubject, "Normal", cloud.VMStateRunning, MachineInstanceRunning) r.Log.Info(MachineInstanceRunning) } r.ReconciliationSubject.Status.Ready = true - } else if r.ReconciliationSubject.Status.InstanceState == "Error" { - r.Recorder.Event(r.ReconciliationSubject, "Warning", "Error", MachineInErrorMessage) + case cloud.VMStateError: + r.Recorder.Event(r.ReconciliationSubject, "Warning", cloud.VMStateError, MachineInErrorMessage) r.Log.Info(MachineInErrorMessage, "csMachine", r.ReconciliationSubject.GetName()) if err := r.K8sClient.Delete(r.RequestCtx, r.CAPIMachine); err != nil { return ctrl.Result{}, err } + return ctrl.Result{RequeueAfter: utils.RequeueTimeout}, nil - } else { + default: r.Recorder.Eventf(r.ReconciliationSubject, "Warning", r.ReconciliationSubject.Status.InstanceState, MachineNotReadyMessage, r.ReconciliationSubject.Status.InstanceState) r.Log.Info(fmt.Sprintf(MachineNotReadyMessage, r.ReconciliationSubject.Status.InstanceState)) + return ctrl.Result{RequeueAfter: utils.RequeueTimeout}, nil } + return ctrl.Result{}, nil } // AddToLBIfNeeded adds instance to load balancer if it is a control plane node in an isolated network, and the load balancer is enabled. -func (r *CloudStackMachineReconciliationRunner) AddToLBIfNeeded() (retRes ctrl.Result, reterr error) { +func (r *CloudStackMachineReconciliationRunner) AddToLBIfNeeded() (ctrl.Result, error) { if util.IsControlPlaneMachine(r.CAPIMachine) && r.FailureDomain.Spec.Zone.Network.Type == cloud.NetworkTypeIsolated && r.CSCluster.Spec.APIServerLoadBalancer.IsEnabled() { @@ -309,7 +318,7 @@ func (r *CloudStackMachineReconciliationRunner) AddToLBIfNeeded() (retRes ctrl.R } // GetOrCreateMachineStateChecker gets or creates a CloudStackMachineStateChecker object. -func (r *CloudStackMachineReconciliationRunner) GetOrCreateMachineStateChecker() (retRes ctrl.Result, reterr error) { +func (r *CloudStackMachineReconciliationRunner) GetOrCreateMachineStateChecker() (ctrl.Result, error) { checkerName := r.ReconciliationSubject.Spec.InstanceID if checkerName == nil { return ctrl.Result{}, errors.New(CSMachineStateCheckerCreationFailed) @@ -326,6 +335,7 @@ func (r *CloudStackMachineReconciliationRunner) GetOrCreateMachineStateChecker() if err := r.K8sClient.Create(r.RequestCtx, csMachineStateChecker); err != nil { r.Recorder.Eventf(r.ReconciliationSubject, "Warning", "Machine State Checker", CSMachineStateCheckerCreationFailed) + return r.ReturnWrappedError(err, CSMachineStateCheckerCreationFailed) } r.Recorder.Eventf(r.ReconciliationSubject, "Normal", "Machine State Checker", CSMachineStateCheckerCreationSuccess) @@ -340,7 +350,7 @@ func (r *CloudStackMachineReconciliationRunner) GetOrCreateMachineStateChecker() return ctrl.Result{}, nil } -func (r *CloudStackMachineReconciliationRunner) ReconcileDelete() (retRes ctrl.Result, reterr error) { +func (r *CloudStackMachineReconciliationRunner) ReconcileDelete() (ctrl.Result, error) { if r.ReconciliationSubject.Spec.InstanceID == nil { // InstanceID is not set until deploying VM finishes which can take minutes, and CloudStack Machine can be deleted before VM deployment complete. // ResolveVMInstanceDetails can get InstanceID by CS machine name @@ -353,6 +363,7 @@ func (r *CloudStackMachineReconciliationRunner) ReconcileDelete() (retRes ctrl.R // Cloudstack VM may be not found or more than one found by name r.Recorder.Eventf(r.ReconciliationSubject, "Warning", "Deleting", CSMachineDeletionInstanceIDNotFoundMessage, r.ReconciliationSubject.Name) r.Log.Error(err, fmt.Sprintf(CSMachineDeletionInstanceIDNotFoundMessage, r.ReconciliationSubject.Name)) + return ctrl.Result{}, err } } @@ -364,13 +375,16 @@ func (r *CloudStackMachineReconciliationRunner) ReconcileDelete() (retRes ctrl.R if err := r.CSClient.DestroyVMInstance(r.ReconciliationSubject); err != nil { if err.Error() == "VM deletion in progress" { r.Log.Info(err.Error()) + return ctrl.Result{RequeueAfter: utils.DestroyVMRequeueInterval}, nil } + return ctrl.Result{}, err } controllerutil.RemoveFinalizer(r.ReconciliationSubject, infrav1.MachineFinalizer) r.Log.Info("VM Deleted", "instanceID", r.ReconciliationSubject.Spec.InstanceID) + return ctrl.Result{}, nil } @@ -401,6 +415,7 @@ func (reconciler *CloudStackMachineReconciler) SetupWithManager(ctx context.Cont oldMachine, ok := e.ObjectOld.(*clusterv1.Machine) if !ok { log.V(4).Info("Expected Machine", "type", fmt.Sprintf("%T", e.ObjectOld)) + return false } newMachine := e.ObjectNew.(*clusterv1.Machine) @@ -469,6 +484,7 @@ func (reconciler *CloudStackMachineReconciler) SetupWithManager(ctx context.Cont oldCSIsoNet, ok := e.ObjectOld.(*infrav1.CloudStackIsolatedNetwork) if !ok { log.V(4).Info("Expected CloudStackIsolatedNetwork", "type", fmt.Sprintf("%T", e.ObjectOld)) + return false } @@ -489,5 +505,6 @@ func (reconciler *CloudStackMachineReconciler) SetupWithManager(ctx context.Cont if err != nil { return errors.Wrap(err, "failed setting up with a controller manager") } + return nil } diff --git a/controllers/cloudstackmachine_controller_test.go b/controllers/cloudstackmachine_controller_test.go index 4cdbe405..abb559d8 100644 --- a/controllers/cloudstackmachine_controller_test.go +++ b/controllers/cloudstackmachine_controller_test.go @@ -27,14 +27,16 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/pointer" - infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" - dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/util/patch" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" + dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" ) var _ = Describe("CloudStackMachineReconciler", func() { @@ -62,7 +64,7 @@ var _ = Describe("CloudStackMachineReconciler", func() { gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Do( func(arg1, _, _, _, _ interface{}) { - arg1.(*infrav1.CloudStackMachine).Status.InstanceState = "Running" + arg1.(*infrav1.CloudStackMachine).Status.InstanceState = cloud.VMStateRunning }).AnyTimes() // Have to do this here or the reconcile call to GetOrCreateVMInstance may happen too early. @@ -77,6 +79,7 @@ var _ = Describe("CloudStackMachineReconciler", func() { return len(tempMachine.ObjectMeta.Finalizers) > 0 } } + return false }, timeout).WithPolling(pollInterval).Should(BeTrue()) }) @@ -87,7 +90,7 @@ var _ = Describe("CloudStackMachineReconciler", func() { gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Do( func(arg1, _, _, _, _ interface{}) { - arg1.(*infrav1.CloudStackMachine).Status.InstanceState = "Running" + arg1.(*infrav1.CloudStackMachine).Status.InstanceState = cloud.VMStateRunning controllerutil.AddFinalizer(arg1.(*infrav1.CloudStackMachine), infrav1.MachineFinalizer) }).AnyTimes() @@ -104,6 +107,7 @@ var _ = Describe("CloudStackMachineReconciler", func() { return true } } + return false }, timeout).WithPolling(pollInterval).Should(BeTrue()) @@ -115,9 +119,9 @@ var _ = Describe("CloudStackMachineReconciler", func() { if err := k8sClient.Get(ctx, key, tempMachine); err != nil { return errors.IsNotFound(err) } + return false }, timeout).WithPolling(pollInterval).Should(BeTrue()) - }) It("Should call ResolveVMInstanceDetails when CS machine without instanceID deleted", func() { @@ -127,7 +131,7 @@ var _ = Describe("CloudStackMachineReconciler", func() { gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Do( func(arg1, _, _, _, _ interface{}) { - arg1.(*infrav1.CloudStackMachine).Status.InstanceState = "Running" + arg1.(*infrav1.CloudStackMachine).Status.InstanceState = cloud.VMStateRunning controllerutil.AddFinalizer(arg1.(*infrav1.CloudStackMachine), infrav1.MachineFinalizer) }).AnyTimes() @@ -149,6 +153,7 @@ var _ = Describe("CloudStackMachineReconciler", func() { return true } } + return false }, timeout).WithPolling(pollInterval).Should(BeTrue()) @@ -165,9 +170,9 @@ var _ = Describe("CloudStackMachineReconciler", func() { if err := k8sClient.Get(ctx, key, tempMachine); err != nil { return errors.IsNotFound(err) } + return false }, timeout).WithPolling(pollInterval).Should(BeTrue()) - }) It("Should replace ds.meta_data.xxx with proper values.", func() { @@ -177,8 +182,8 @@ var _ = Describe("CloudStackMachineReconciler", func() { gomock.Any(), gomock.Any()).Do( func(arg1, _, _, _, userdata interface{}) { expectedUserdata := fmt.Sprintf("%s{{%s}}", dummies.CAPIMachine.Name, dummies.CSMachine1.Spec.FailureDomainName) - Ω(userdata == expectedUserdata).Should(BeTrue()) - arg1.(*infrav1.CloudStackMachine).Status.InstanceState = "Running" + Ω(userdata).Should(BeIdenticalTo(expectedUserdata)) + arg1.(*infrav1.CloudStackMachine).Status.InstanceState = cloud.VMStateRunning }).AnyTimes() // Have to do this here or the reconcile call to GetOrCreateVMInstance may happen too early. @@ -193,6 +198,7 @@ var _ = Describe("CloudStackMachineReconciler", func() { return true } } + return false }, timeout).WithPolling(pollInterval).Should(BeTrue()) }) @@ -238,7 +244,7 @@ var _ = Describe("CloudStackMachineReconciler", func() { gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Do( func(arg1, _, _, _, _ interface{}) { - arg1.(*infrav1.CloudStackMachine).Status.InstanceState = "Running" + arg1.(*infrav1.CloudStackMachine).Status.InstanceState = cloud.VMStateRunning }).AnyTimes() Ω(fakeCtrlClient.Get(ctx, key, dummies.CSCluster)).Should(Succeed()) Ω(fakeCtrlClient.Create(ctx, dummies.CAPIMachine)).Should(Succeed()) @@ -261,6 +267,7 @@ var _ = Describe("CloudStackMachineReconciler", func() { strings.Contains(event, "Normal Running Machine instance is Running...") || strings.Contains(event, "Normal Machine State Checker CloudStackMachineStateChecker created") } + return false }, timeout).Should(BeTrue()) }) diff --git a/controllers/cloudstackmachinestatechecker_controller.go b/controllers/cloudstackmachinestatechecker_controller.go index bb2b4949..2fb6d56d 100644 --- a/controllers/cloudstackmachinestatechecker_controller.go +++ b/controllers/cloudstackmachinestatechecker_controller.go @@ -21,13 +21,14 @@ import ( "strings" "time" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + "sigs.k8s.io/cluster-api/util/predicates" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" csCtrlrUtils "sigs.k8s.io/cluster-api-provider-cloudstack/controllers/utils" - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" - "sigs.k8s.io/cluster-api/util/predicates" + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" ) //+kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=cloudstackmachinestatecheckers,verbs=get;list;watch;create;update;patch;delete @@ -45,7 +46,7 @@ type CloudStackMachineStateCheckerReconciliationRunner struct { CSMachine *infrav1.CloudStackMachine } -// CloudStackMachineStateCheckerReconciler reconciles a CloudStackMachineStateChecker object +// CloudStackMachineStateCheckerReconciler reconciles a CloudStackMachineStateChecker object. type CloudStackMachineStateCheckerReconciler struct { csCtrlrUtils.ReconcilerBase } @@ -59,6 +60,7 @@ func NewCSMachineStateCheckerReconciliationRunner() *CloudStackMachineStateCheck runner.FailureDomain = &infrav1.CloudStackFailureDomain{} // Setup the base runner. Initializes pointers and links reconciliation methods. runner.ReconciliationRunner = csCtrlrUtils.NewRunner(runner, runner.ReconciliationSubject, "CloudStackMachineStateChecker") + return runner } @@ -86,7 +88,7 @@ func (r *CloudStackMachineStateCheckerReconciliationRunner) Reconcile() (ctrl.Re // capiTimeout indicates that a new VM is running, but it isn't reachable. // The cluster may not recover if the machine isn't replaced. - csRunning := r.CSMachine.Status.InstanceState == "Running" + csRunning := r.CSMachine.Status.InstanceState == cloud.VMStateRunning csTimeInState := r.CSMachine.Status.TimeSinceLastStateChange() capiRunning := r.CAPIMachine.Status.Phase == "Running" capiTimeout := csRunning && !capiRunning && csTimeInState > 5*time.Minute diff --git a/controllers/controllers_suite_test.go b/controllers/controllers_suite_test.go index fce90ca2..f674b3f0 100644 --- a/controllers/controllers_suite_test.go +++ b/controllers/controllers_suite_test.go @@ -19,7 +19,6 @@ package controllers_test import ( "context" "flag" - "fmt" "go/build" "os" "path" @@ -30,9 +29,6 @@ import ( "testing" "time" - "k8s.io/client-go/tools/record" - "sigs.k8s.io/cluster-api-provider-cloudstack/test/fakes" - "github.com/apache/cloudstack-go/v2/cloudstack" "github.com/go-logr/logr" "github.com/golang/mock/gomock" @@ -41,7 +37,10 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" "k8s.io/klog/v2" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + "sigs.k8s.io/cluster-api/util/patch" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -52,21 +51,17 @@ import ( csReconcilers "sigs.k8s.io/cluster-api-provider-cloudstack/controllers" csCtrlrUtils "sigs.k8s.io/cluster-api-provider-cloudstack/controllers/utils" "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/mocks" - dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" - "sigs.k8s.io/cluster-api/util/patch" - //+kubebuilder:scaffold:imports + "sigs.k8s.io/cluster-api-provider-cloudstack/test/fakes" ) -func TestAPIs(t *testing.T) { +func TestControllers(t *testing.T) { RegisterFailHandler(Fail) + RunSpecs(t, "Controller Suite") } -var ( - clusterAPIVersionRegex = regexp.MustCompile(`^(\W)sigs.k8s.io/cluster-api v(.+)`) -) +var clusterAPIVersionRegex = regexp.MustCompile(`^(\W)sigs.k8s.io/cluster-api v(.+)`) const ( timeout = 10 * time.Second @@ -78,6 +73,7 @@ func envOr(envKey, defaultValue string) string { if value, ok := os.LookupEnv(envKey); ok { return value } + return defaultValue } @@ -101,12 +97,13 @@ func getFilePathToCAPICRDs(root string) string { } gopath := envOr("GOPATH", build.Default.GOPATH) + return filepath.Join(gopath, "pkg", "mod", "sigs.k8s.io", - fmt.Sprintf("cluster-api@v%s", clusterAPIVersion), "config", "crd", "bases") + "cluster-api@v"+clusterAPIVersion, "config", "crd", "bases") } var ( - // TestEnv vars... + // TestEnv vars. testEnv *envtest.Environment k8sClient client.Client ctx context.Context @@ -122,7 +119,7 @@ var ( mockCloudClient *mocks.MockClient mockCSAPIClient *cloudstack.CloudStackClient - // Reconcilers + // Reconcilers. MachineReconciler *csReconcilers.CloudStackMachineReconciler ClusterReconciler *csReconcilers.CloudStackClusterReconciler FailureDomainReconciler *csReconcilers.CloudStackFailureDomainReconciler @@ -154,7 +151,8 @@ type MockCtrlrCloudClientImplementation struct { // AsFailureDomainUser is a method used in the reconciliation runner to set up the CloudStack client. Using this here // just sets the CSClient to a mock client. func (m *MockCtrlrCloudClientImplementation) AsFailureDomainUser( - *infrav1.CloudStackFailureDomainSpec) csCtrlrUtils.CloudStackReconcilerMethod { + *infrav1.CloudStackFailureDomainSpec, +) csCtrlrUtils.CloudStackReconcilerMethod { return func() (ctrl.Result, error) { m.CSUser = mockCloudClient @@ -230,8 +228,7 @@ func SetupTestEnvironment() { setupClusterCRDs() - // See reconciliation results. Left commented as it's noisy otherwise. - // TODO: find a way to see controller output without the additional setup output. + // See reconciliation results. ctrl.SetLogger(logger) DeferCleanup(func() { @@ -313,13 +310,13 @@ func setClusterReady(client client.Client) { ph, err := patch.NewHelper(dummies.CSCluster, client) Ω(err).ShouldNot(HaveOccurred()) dummies.CSCluster.Status.Ready = true + return ph.Patch(ctx, dummies.CSCluster, patch.WithStatusObservedGeneration{}) }, timeout).Should(Succeed()) } // setupClusterCRDs creates a CAPI and CloudStack cluster with an appropriate ownership ref between them. func setupClusterCRDs() { - // Create them. Ω(k8sClient.Create(ctx, dummies.CAPICluster)).Should(Succeed()) Ω(k8sClient.Create(ctx, dummies.CSCluster)).Should(Succeed()) @@ -340,6 +337,7 @@ func setupClusterCRDs() { Name: dummies.CAPICluster.Name, UID: "uniqueness", }) + return ph.Patch(ctx, dummies.CSCluster, patch.WithStatusObservedGeneration{}) }, timeout).Should(Succeed()) } @@ -366,6 +364,7 @@ func setupMachineCRDs() { Name: dummies.CAPIMachine.Name, UID: "uniqueness", }) + return ph.Patch(ctx, dummies.CSMachine1, patch.WithStatusObservedGeneration{}) }, timeout).Should(Succeed()) } @@ -382,26 +381,27 @@ func setCSMachineOwnerCRD(owner *fakes.CloudStackMachineOwner, specReplicas, sta owner.Status.Ready = statusReady owner.Status.Replicas = statusReplicas owner.Status.ReadyReplicas = statusReadyReplicas + return k8sClient.Status().Update(ctx, owner) }, timeout).Should(BeNil()) } // setCAPIMachineAndCSMachineCRDs creates a CAPI and CloudStack machine with an appropriate ownership ref between them. -func setCAPIMachineAndCSMachineCRDs(CSMachine *infrav1.CloudStackMachine, CAPIMachine *clusterv1.Machine) { +func setCAPIMachineAndCSMachineCRDs(csMachine *infrav1.CloudStackMachine, capiMachine *clusterv1.Machine) { // Create them. - Ω(k8sClient.Create(ctx, CAPIMachine)).Should(Succeed()) - Ω(k8sClient.Create(ctx, CSMachine)).Should(Succeed()) + Ω(k8sClient.Create(ctx, capiMachine)).Should(Succeed()) + Ω(k8sClient.Create(ctx, csMachine)).Should(Succeed()) // Fetch the CS Machine that was created. - key := client.ObjectKey{Namespace: dummies.CSCluster.Namespace, Name: CSMachine.Name} + key := client.ObjectKey{Namespace: dummies.CSCluster.Namespace, Name: csMachine.Name} Eventually(func() error { - return k8sClient.Get(ctx, key, CSMachine) + return k8sClient.Get(ctx, key, csMachine) }, timeout).Should(BeNil()) // Fetch the CAPI Machine that was created. - key = client.ObjectKey{Namespace: dummies.ClusterNameSpace, Name: CAPIMachine.Name} + key = client.ObjectKey{Namespace: dummies.ClusterNameSpace, Name: capiMachine.Name} Eventually(func() error { - return k8sClient.Get(ctx, key, CAPIMachine) + return k8sClient.Get(ctx, key, capiMachine) }, timeout).Should(BeNil()) // Set ownerReference to CAPI machine in CS machine and patch back the CS machine. @@ -411,40 +411,43 @@ func setCAPIMachineAndCSMachineCRDs(CSMachine *infrav1.CloudStackMachine, CAPIMa dummies.CSMachine1.OwnerReferences = append(dummies.CSMachine1.OwnerReferences, metav1.OwnerReference{ Kind: "Machine", APIVersion: clusterv1.GroupVersion.String(), - Name: CAPIMachine.Name, + Name: capiMachine.Name, UID: "uniqueness", }) - return ph.Patch(ctx, CSMachine, patch.WithStatusObservedGeneration{}) + + return ph.Patch(ctx, csMachine, patch.WithStatusObservedGeneration{}) }, timeout).Should(Succeed()) } -func setMachineOwnerReference(CSMachine *infrav1.CloudStackMachine, ownerRef metav1.OwnerReference) { - key := client.ObjectKey{Namespace: dummies.CSCluster.Namespace, Name: CSMachine.Name} +func setMachineOwnerReference(csMachine *infrav1.CloudStackMachine, ownerRef metav1.OwnerReference) { + key := client.ObjectKey{Namespace: dummies.CSCluster.Namespace, Name: csMachine.Name} Eventually(func() error { - return k8sClient.Get(ctx, key, CSMachine) + return k8sClient.Get(ctx, key, csMachine) }, timeout).Should(BeNil()) // Set ownerReference to CAPI machine in CS machine and patch back the CS machine. Eventually(func() error { - ph, err := patch.NewHelper(CSMachine, k8sClient) + ph, err := patch.NewHelper(csMachine, k8sClient) Ω(err).ShouldNot(HaveOccurred()) - CSMachine.OwnerReferences = append(CSMachine.OwnerReferences, ownerRef) - return ph.Patch(ctx, CSMachine, patch.WithStatusObservedGeneration{}) + csMachine.OwnerReferences = append(csMachine.OwnerReferences, ownerRef) + + return ph.Patch(ctx, csMachine, patch.WithStatusObservedGeneration{}) }, timeout).Should(Succeed()) } // labelMachineFailuredomain add cloudstackfailuredomain info in the labels. -func labelMachineFailuredomain(CSMachine *infrav1.CloudStackMachine, CSFailureDomain1 *infrav1.CloudStackFailureDomain) { - key := client.ObjectKey{Namespace: dummies.CSCluster.Namespace, Name: CSMachine.Name} +func labelMachineFailuredomain(csMachine *infrav1.CloudStackMachine, csFailureDomain1 *infrav1.CloudStackFailureDomain) { + key := client.ObjectKey{Namespace: dummies.CSCluster.Namespace, Name: csMachine.Name} Eventually(func() error { - return k8sClient.Get(ctx, key, CSMachine) + return k8sClient.Get(ctx, key, csMachine) }, timeout).Should(BeNil()) // set cloudstack failuredomain in machine labels. Eventually(func() error { - ph, err := patch.NewHelper(CSMachine, k8sClient) + ph, err := patch.NewHelper(csMachine, k8sClient) Ω(err).ShouldNot(HaveOccurred()) - CSMachine.Labels["cloudstackfailuredomain.infrastructure.cluster.x-k8s.io/name"] = CSFailureDomain1.Name - return ph.Patch(ctx, CSMachine, patch.WithStatusObservedGeneration{}) + csMachine.Labels["cloudstackfailuredomain.infrastructure.cluster.x-k8s.io/name"] = csFailureDomain1.Name + + return ph.Patch(ctx, csMachine, patch.WithStatusObservedGeneration{}) }, timeout).Should(Succeed()) } diff --git a/controllers/utils/affinity_group.go b/controllers/utils/affinity_group.go index 88a78112..14f95b8e 100644 --- a/controllers/utils/affinity_group.go +++ b/controllers/utils/affinity_group.go @@ -20,15 +20,15 @@ import ( "fmt" "strings" + "github.com/pkg/errors" "golang.org/x/text/cases" "golang.org/x/text/language" - - "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) // GetOrCreateAffinityGroup of the passed name that's owned by the failure domain of the reconciliation subject and @@ -37,8 +37,8 @@ func (r *ReconciliationRunner) GetOrCreateAffinityGroup( name string, affinityType string, ag *infrav1.CloudStackAffinityGroup, - fd *infrav1.CloudStackFailureDomain) CloudStackReconcilerMethod { - + fd *infrav1.CloudStackFailureDomain, +) CloudStackReconcilerMethod { return func() (ctrl.Result, error) { // Start by attempting a fetch. lowerName := strings.ToLower(name) @@ -51,15 +51,16 @@ func (r *ReconciliationRunner) GetOrCreateAffinityGroup( } // Didn't find a group, so create instead. // Set affinity group type. - if affinityType == infrav1.ProAffinity { + switch affinityType { + case infrav1.AffinityTypePro: ag.Spec.Type = "host affinity" - } else if affinityType == infrav1.AntiAffinity { + case infrav1.AffinityTypeAnti: ag.Spec.Type = "host anti-affinity" - } else if affinityType == infrav1.SoftProAffinity { + case infrav1.AffinityTypeSoftPro: ag.Spec.Type = "non-strict host affinity" - } else if affinityType == infrav1.SoftAntiAffinity { + case infrav1.AffinityTypeSoftAnti: ag.Spec.Type = "non-strict host anti-affinity" - } else { + default: return ctrl.Result{}, errors.Errorf("unrecognized affinity type %s", affinityType) } @@ -74,6 +75,7 @@ func (r *ReconciliationRunner) GetOrCreateAffinityGroup( strings.EqualFold(ref.Kind, "KubeadmControlPlane") || strings.EqualFold(ref.Kind, "MachineSet") { ag.OwnerReferences = []metav1.OwnerReference{ref} + break } } @@ -88,6 +90,7 @@ func (r *ReconciliationRunner) GetOrCreateAffinityGroup( if err := r.K8sClient.Create(r.RequestCtx, ag); err != nil && !ContainsAlreadyExistsSubstring(err) { return r.ReturnWrappedError(err, "creating affinity group CRD") } + return ctrl.Result{}, nil } } @@ -106,6 +109,7 @@ func GenerateAffinityGroupName(csm infrav1.CloudStackMachine, capiMachine *clust return fmt.Sprintf("%s-%s-%sAffinity-%s-%s", capiCluster.Name, capiCluster.UID, titleCaser.String(csm.Spec.Affinity), managerOwnerRef.Kind, csm.Spec.FailureDomainName), nil } + return fmt.Sprintf("%s-%s-%sAffinity-%s-%s-%s", capiCluster.Name, capiCluster.UID, titleCaser.String(csm.Spec.Affinity), managerOwnerRef.Name, managerOwnerRef.UID, csm.Spec.FailureDomainName), nil } diff --git a/controllers/utils/base_reconciler.go b/controllers/utils/base_reconciler.go index b27c5f76..52a7fafc 100644 --- a/controllers/utils/base_reconciler.go +++ b/controllers/utils/base_reconciler.go @@ -22,8 +22,6 @@ import ( "strings" "time" - "k8s.io/client-go/tools/record" - "github.com/go-logr/logr" "github.com/hashicorp/go-multierror" "github.com/pkg/errors" @@ -31,14 +29,16 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" - "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" + "k8s.io/client-go/tools/record" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/util/annotations" "sigs.k8s.io/cluster-api/util/patch" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" ) // ReconcilerBase is the base set of components we use in k8s reconcilers. @@ -92,6 +92,7 @@ func NewRunner(concreteRunner ConcreteRunner, subject client.Object, kind string r.Reconcile = concreteRunner.Reconcile r.ReconcileDelete = concreteRunner.ReconcileDelete r.ControllerKind = kind + return &r } @@ -105,18 +106,21 @@ func (r *ReconciliationRunner) UsingBaseReconciler(base ReconcilerBase) *Reconci } else { r.CloudClientExtension = base.CloudClientExtension.RegisterExtension(r) } + return r } // ForRequest sets the reconciliation request. func (r *ReconciliationRunner) ForRequest(req ctrl.Request) *ReconciliationRunner { r.Request = req + return r } // WithRequestCtx sets the request context. func (r *ReconciliationRunner) WithRequestCtx(ctx context.Context) *ReconciliationRunner { r.RequestCtx = ctx + return r } @@ -124,14 +128,16 @@ func (r *ReconciliationRunner) WithRequestCtx(ctx context.Context) *Reconciliati // Reconcile() and ReconcileDelete(). func (r *ReconciliationRunner) WithAdditionalCommonStages(fns ...CloudStackReconcilerMethod) *ReconciliationRunner { r.additionalCommonStages = fns + return r } // SetupLogger sets up the reconciler's logger to log with name and namespace values. -func (r *ReconciliationRunner) SetupLogger() (res ctrl.Result, retErr error) { +func (r *ReconciliationRunner) SetupLogger() (ctrl.Result, error) { log := ctrl.LoggerFrom(r.RequestCtx).WithName("controllers") r.Log = log.WithName(r.ControllerKind) r.Log.V(1).Info("Logger setup complete.") + return ctrl.Result{}, nil } @@ -147,6 +153,7 @@ func (r *ReconciliationRunner) RunIf(conditional func() bool, fn CloudStackRecon if placeholder { return fn() } + return ctrl.Result{}, nil } } @@ -157,6 +164,7 @@ func (r *ReconciliationRunner) Else(fn CloudStackReconcilerMethod) CloudStackRec if !r.ConditionalResult { return fn() } + return ctrl.Result{}, nil } } @@ -168,6 +176,7 @@ func (r *ReconciliationRunner) GetCAPICluster() (ctrl.Result, error) { if name == "" { r.Log.V(1).Info("Reconciliation Subject is missing cluster label or cluster does not exist. Skipping CAPI Cluster fetch.", "SubjectKind", r.ReconciliationSubject.GetObjectKind().GroupVersionKind().Kind) + return ctrl.Result{}, nil } r.CAPICluster = &clusterv1.Cluster{} @@ -180,6 +189,7 @@ func (r *ReconciliationRunner) GetCAPICluster() (ctrl.Result, error) { } else if r.CAPICluster.Name == "" { return r.RequeueWithMessage("Cluster not fetched.") } + return ctrl.Result{}, nil } @@ -190,6 +200,7 @@ func (r *ReconciliationRunner) GetCSCluster() (ctrl.Result, error) { if name == "" { r.Log.V(1).Info("Reconciliation Subject is missing cluster label or cluster does not exist. Skipping CloudStackCluster fetch.", "SubjectKind", r.ReconciliationSubject.GetObjectKind().GroupVersionKind().Kind) + return ctrl.Result{}, nil } r.CSCluster = &infrav1.CloudStackCluster{} @@ -198,11 +209,12 @@ func (r *ReconciliationRunner) GetCSCluster() (ctrl.Result, error) { Name: name, } err := r.K8sClient.Get(r.RequestCtx, key, r.CSCluster) + return ctrl.Result{}, errors.Wrapf(client.IgnoreNotFound(err), "getting CAPI Cluster %s", name) } // CheckOwnedCRDsForReadiness queries for the readiness of CRDs of GroupVersionKind passed. -func (r *ReconciliationRunner) CheckOwnedCRDsForReadiness(gvks ...schema.GroupVersionKind) CloudStackReconcilerMethod { +func (r *ReconciliationRunner) CheckOwnedCRDsForReadiness(gvks ...schema.GroupVersionKind) CloudStackReconcilerMethod { //nolint:gocognit return func() (ctrl.Result, error) { // For each GroupVersionKind... for _, gvk := range gvks { @@ -224,7 +236,6 @@ func (r *ReconciliationRunner) CheckOwnedCRDsForReadiness(gvks ...schema.GroupVe ownedObjs = append(ownedObjs, pOwned) } } - } // Check that found objects are ready. @@ -242,11 +253,13 @@ func (r *ReconciliationRunner) CheckOwnedCRDsForReadiness(gvks ...schema.GroupVe owned.GetName())) } else { r.Log.Info(fmt.Sprintf("Owned object %s of kind %s not ready, requeuing", name, gvk.Kind)) + return ctrl.Result{RequeueAfter: RequeueTimeout}, nil } } } } + return ctrl.Result{}, nil } } @@ -278,6 +291,7 @@ func (r *ReconciliationRunner) DeleteOwnedObjects(gvks ...schema.GroupVersionKin } } } + return ctrl.Result{}, nil } } @@ -307,6 +321,7 @@ func (r *ReconciliationRunner) CheckOwnedObjectsDeleted(gvks ...schema.GroupVers } } } + return ctrl.Result{}, nil } } @@ -315,26 +330,31 @@ func (r *ReconciliationRunner) CheckOwnedObjectsDeleted(gvks ...schema.GroupVers func (r *ReconciliationRunner) RequeueIfCloudStackClusterNotReady() (ctrl.Result, error) { if r.CSCluster.DeletionTimestamp.IsZero() && !r.CSCluster.Status.Ready { r.Log.Info("CloudStackCluster not ready. Requeuing.") + return ctrl.Result{RequeueAfter: RequeueTimeout}, nil } + return ctrl.Result{}, nil } // SetupPatcher initializes the patcher with the ReconciliationSubject. // This must be done before changes to the ReconciliationSubject for changes to be patched back later. -func (r *ReconciliationRunner) SetupPatcher() (res ctrl.Result, retErr error) { +func (r *ReconciliationRunner) SetupPatcher() (ctrl.Result, error) { + var err error r.Log.V(1).Info("Setting up patcher.") - r.Patcher, retErr = patch.NewHelper(r.ReconciliationSubject, r.K8sClient) - return res, errors.Wrapf(retErr, "setting up patcher") + r.Patcher, err = patch.NewHelper(r.ReconciliationSubject, r.K8sClient) + + return ctrl.Result{}, errors.Wrapf(err, "setting up patcher") } // RequeueWithMessage is a convenience method to log requeue message and then return a result with RequeueAfter set. func (r *ReconciliationRunner) RequeueWithMessage(msg string, keysAndValues ...interface{}) (ctrl.Result, error) { // Add requeuing to message if not present. Might turn this into a lint check later. if !strings.Contains(strings.ToLower(msg), "requeu") { - msg = msg + " Requeuing." + msg += " Requeuing." } r.Log.Info(msg, keysAndValues...) + return ctrl.Result{RequeueAfter: RequeueTimeout}, nil } @@ -355,6 +375,7 @@ func (r *ReconciliationRunner) ShouldReturn(rslt ctrl.Result, err error) bool { } else if rslt.Requeue || rslt.RequeueAfter != time.Duration(0) { return true } + return false } @@ -374,9 +395,9 @@ func (r *ReconciliationRunner) RunReconciliationStages(fns ...CloudStackReconcil return ctrl.Result{}, nil } -// RunBaseReconciliationStages runs the base reconciliation stages which are to setup the logger, get the reconciliation +// RunBaseReconciliationStages runs the base reconciliation stages which are to set up the logger, get the reconciliation // subject, get CAPI and CloudStackClusters, and call either r.Reconcile or r.ReconcileDelete. -func (r *ReconciliationRunner) RunBaseReconciliationStages() (res ctrl.Result, retErr error) { +func (r *ReconciliationRunner) RunBaseReconciliationStages() (res ctrl.Result, retErr error) { //nolint:nonamedreturns defer func() { if r.Patcher != nil { if err := r.Patcher.Patch(r.RequestCtx, r.ReconciliationSubject); err != nil { @@ -396,7 +417,8 @@ func (r *ReconciliationRunner) RunBaseReconciliationStages() (res ctrl.Result, r r.GetCAPICluster, r.GetCSCluster, r.RunIf(func() bool { return r.ReconciliationSubject.GetDeletionTimestamp().IsZero() }, r.RequeueIfMissingBaseCRs), - r.CheckIfPaused} + r.CheckIfPaused, + } baseStages = append( append(baseStages, r.additionalCommonStages...), r.RunIf(func() bool { return r.ReconciliationSubject.GetDeletionTimestamp().IsZero() }, r.Reconcile), @@ -411,6 +433,7 @@ func (r *ReconciliationRunner) CheckIfPaused() (ctrl.Result, error) { if annotations.IsPaused(r.CAPICluster, r.ReconciliationSubject) { return r.RequeueWithMessage("Cluster is paused. Refusing to reconcile.") } + return reconcile.Result{}, nil } @@ -422,7 +445,7 @@ func (r *ReconciliationRunner) SetReturnEarly() { // GetReconciliationSubject gets the reconciliation subject of type defined by the concrete reconciler. It also sets up // a patch helper at this point. -func (r *ReconciliationRunner) GetReconciliationSubject() (res ctrl.Result, reterr error) { +func (r *ReconciliationRunner) GetReconciliationSubject() (ctrl.Result, error) { r.Log.V(1).Info("Getting reconciliation subject.") err := client.IgnoreNotFound(r.K8sClient.Get(r.RequestCtx, r.Request.NamespacedName, r.ReconciliationSubject)) if r.ReconciliationSubject.GetName() == "" { // Resource does not exist. No need to reconcile. @@ -432,6 +455,7 @@ func (r *ReconciliationRunner) GetReconciliationSubject() (res ctrl.Result, rete if err != nil { return ctrl.Result{}, errors.Wrap(err, "fetching reconciliation subject") } + return ctrl.Result{}, nil } @@ -439,6 +463,7 @@ func (r *ReconciliationRunner) GetReconciliationSubject() (res ctrl.Result, rete func (r *ReconciliationRunner) SetReconciliationSubjectToConcreteSubject(subject client.Object) CloudStackReconcilerMethod { return func() (ctrl.Result, error) { r.ReconciliationSubject = subject + return ctrl.Result{}, nil } } @@ -447,6 +472,7 @@ func (r *ReconciliationRunner) SetReconciliationSubjectToConcreteSubject(subject func (r *ReconciliationRunner) GetParent(child client.Object, parent client.Object) CloudStackReconcilerMethod { return func() (ctrl.Result, error) { err := GetOwnerOfKind(r.RequestCtx, r.K8sClient, child, parent) + return ctrl.Result{}, err } } @@ -455,6 +481,7 @@ func (r *ReconciliationRunner) GetParent(child client.Object, parent client.Obje func (r *ReconciliationRunner) GetOwnerOfKind(owner client.Object) CloudStackReconcilerMethod { return func() (ctrl.Result, error) { err := GetOwnerOfKind(r.RequestCtx, r.K8sClient, r.ReconciliationSubject, owner) + return ctrl.Result{}, err } } @@ -462,6 +489,7 @@ func (r *ReconciliationRunner) GetOwnerOfKind(owner client.Object) CloudStackRec // NewChildObjectMeta creates a meta object with ownership reference and labels matching the current cluster. func (r *ReconciliationRunner) NewChildObjectMeta(name string) metav1.ObjectMeta { ownerGVK := r.ReconciliationSubject.GetObjectKind().GroupVersionKind() + return metav1.ObjectMeta{ Name: strings.ToLower(name), Namespace: r.Request.Namespace, @@ -481,6 +509,7 @@ func (r *ReconciliationRunner) RequeueIfMissingBaseCRs() (ctrl.Result, error) { } else if r.CAPICluster.GetName() == "" { return r.RequeueWithMessage("CAPI Cluster wasn't found. Requeuing.") } + return ctrl.Result{}, nil } @@ -492,6 +521,7 @@ func (r *ReconciliationRunner) GetObjectByName(name string, target client.Object name = nameGetter[0]() } objectKey := client.ObjectKey{Name: strings.ToLower(name), Namespace: r.Request.Namespace} + return r.ReturnWrappedError( client.IgnoreNotFound(r.K8sClient.Get(r.RequestCtx, objectKey, target)), "failed to get object") } @@ -502,9 +532,10 @@ func (r *ReconciliationRunner) CheckPresent(objs map[string]client.Object) Cloud return func() (ctrl.Result, error) { for kind, obj := range objs { if obj.GetName() == "" { - return r.RequeueWithMessage(fmt.Sprintf("missing dependent object of kind %s", kind)) + return r.RequeueWithMessage("missing dependent object of kind " + kind) } } + return ctrl.Result{}, nil } } diff --git a/controllers/utils/constants.go b/controllers/utils/constants.go index c414d50e..319df797 100644 --- a/controllers/utils/constants.go +++ b/controllers/utils/constants.go @@ -18,5 +18,7 @@ package utils import "time" -const RequeueTimeout = 5 * time.Second -const DestroyVMRequeueInterval = 10 * time.Second +const ( + RequeueTimeout = 5 * time.Second + DestroyVMRequeueInterval = 10 * time.Second +) diff --git a/controllers/utils/failuredomains.go b/controllers/utils/failuredomains.go index 542bb407..22d535f7 100644 --- a/controllers/utils/failuredomains.go +++ b/controllers/utils/failuredomains.go @@ -20,15 +20,15 @@ import ( "fmt" "strings" - corev1 "k8s.io/api/core/v1" - infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" - "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" - "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" ) // CreateFailureDomain creates a specified CloudStackFailureDomain CRD owned by the ReconcilationSubject. @@ -38,6 +38,7 @@ func (r *ReconciliationRunner) CreateFailureDomain(fdSpec infrav1.CloudStackFail ObjectMeta: r.NewChildObjectMeta(metaHashName), Spec: fdSpec, } + return errors.Wrap(r.K8sClient.Create(r.RequestCtx, csFD), "creating CloudStackFailureDomain") } @@ -51,6 +52,7 @@ func (r *ReconciliationRunner) CreateFailureDomains(fdSpecs []infrav1.CloudStack } } } + return ctrl.Result{}, nil } } @@ -67,6 +69,7 @@ func (r *ReconciliationRunner) GetFailureDomains(fds *infrav1.CloudStackFailureD ); err != nil { return ctrl.Result{}, errors.Wrap(err, "failed to list failure domains") } + return ctrl.Result{}, nil } } @@ -78,6 +81,7 @@ func (r *ReconciliationRunner) GetFailureDomainByName(nameFunc func() string, fd if err := r.K8sClient.Get(r.RequestCtx, client.ObjectKey{Namespace: r.Request.Namespace, Name: metaHashName}, fd); err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to get failure domain with name %s", nameFunc()) } + return ctrl.Result{}, nil } } @@ -102,6 +106,7 @@ func (r *ReconciliationRunner) RemoveExtraneousFailureDomains(fds *infrav1.Cloud } } } + return ctrl.Result{}, nil } } @@ -114,13 +119,14 @@ func (r *ReconciliationRunner) GetFailureDomainsAndRequeueIfMissing(fds *infrav1 } else if len(fds.Items) < 1 { return r.RequeueWithMessage("no failure domains found, requeueing") } + return ctrl.Result{}, nil } } type CloudClientExtension interface { - RegisterExtension(*ReconciliationRunner) CloudClientExtension - AsFailureDomainUser(*infrav1.CloudStackFailureDomainSpec) CloudStackReconcilerMethod + RegisterExtension(r *ReconciliationRunner) CloudClientExtension + AsFailureDomainUser(fdSpec *infrav1.CloudStackFailureDomainSpec) CloudStackReconcilerMethod } type CloudClientImplementation struct { @@ -130,6 +136,7 @@ type CloudClientImplementation struct { func (c *CloudClientImplementation) RegisterExtension(r *ReconciliationRunner) CloudClientExtension { c.ReconciliationRunner = r + return c } diff --git a/controllers/utils/isolated_network.go b/controllers/utils/isolated_network.go index 3c6bc018..2b1f115e 100644 --- a/controllers/utils/isolated_network.go +++ b/controllers/utils/isolated_network.go @@ -21,14 +21,16 @@ import ( "regexp" "strings" - infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ctrl "sigs.k8s.io/controller-runtime" + + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) var metaNameRegex = regexp.MustCompile(`[^a-z0-9-]+`) func (r *ReconciliationRunner) IsoNetMetaName(name string) string { str := metaNameRegex.ReplaceAllString(fmt.Sprintf("%s-%s", r.CSCluster.Name, strings.ToLower(name)), "-") + return strings.TrimSuffix(str, "-") } @@ -53,6 +55,7 @@ func (r *ReconciliationRunner) GenerateIsolatedNetwork(network infrav1.Network, if err := r.K8sClient.Create(r.RequestCtx, csIsoNet); err != nil && !ContainsAlreadyExistsSubstring(err) { return r.ReturnWrappedError(err, "creating isolated network CRD") } + return ctrl.Result{}, nil } } diff --git a/controllers/utils/utils.go b/controllers/utils/utils.go index cbeee225..c5586227 100644 --- a/controllers/utils/utils.go +++ b/controllers/utils/utils.go @@ -21,17 +21,13 @@ import ( "fmt" "strings" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "github.com/go-logr/logr" "github.com/pkg/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" - infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/util" ctrl "sigs.k8s.io/controller-runtime" @@ -40,6 +36,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) // getMachineSetFromCAPIMachine attempts to fetch a MachineSet from CAPI machine owner reference. @@ -51,6 +50,7 @@ func fetchOwnerRef(refList []metav1.OwnerReference, kind string) *metav1.OwnerRe return &ref } } + return nil } @@ -61,6 +61,7 @@ func GetManagementOwnerRef(capiMachine *clusterv1.Machine) *metav1.OwnerReferenc } else if ref := fetchOwnerRef(capiMachine.OwnerReferences, "EtcdadmCluster"); ref != nil { return ref } + return fetchOwnerRef(capiMachine.OwnerReferences, "MachineSet") } @@ -83,6 +84,7 @@ func GetOwnerOfKind(ctx context.Context, c client.Client, owned client.Object, o if err := c.Get(ctx, key, owner); err != nil { return errors.Wrapf(err, "finding owner of kind %s in namespace %s", gvk.Kind, owned.GetNamespace()) } + return nil } @@ -111,6 +113,7 @@ func GetOwnerClusterName(obj metav1.ObjectMeta) (string, error) { return ref.Name, nil } } + return "", errors.New("failed to get owner Cluster name") } @@ -121,6 +124,7 @@ func CloudStackClusterToCloudStackMachines(c client.Client, log logr.Logger) han csCluster, ok := o.(*infrav1.CloudStackCluster) if !ok { log.Error(fmt.Errorf("expected a CloudStackCluster but got a %T", o), "Error in CloudStackClusterToCloudStackMachines") + return nil } @@ -129,12 +133,14 @@ func CloudStackClusterToCloudStackMachines(c client.Client, log logr.Logger) han // Don't handle deleted CloudStackClusters if !csCluster.ObjectMeta.DeletionTimestamp.IsZero() { log.V(4).Info("CloudStackCluster has a deletion timestamp, skipping mapping.") + return nil } clusterName, err := GetOwnerClusterName(csCluster.ObjectMeta) if err != nil { log.Error(err, "Failed to get owning cluster, skipping mapping.") + return nil } @@ -142,6 +148,7 @@ func CloudStackClusterToCloudStackMachines(c client.Client, log logr.Logger) han // list all the requested objects within the cluster namespace with the cluster name label if err := c.List(ctx, machineList, client.InNamespace(csCluster.Namespace), client.MatchingLabels{clusterv1.ClusterNameLabel: clusterName}); err != nil { log.Error(err, "Failed to get owned Machines, skipping mapping.") + return nil } @@ -151,10 +158,12 @@ func CloudStackClusterToCloudStackMachines(c client.Client, log logr.Logger) han log.WithValues("machine", klog.KObj(&m)) if m.Spec.InfrastructureRef.GroupVersionKind().Kind != "CloudStackMachine" { log.V(4).Info("Machine has an InfrastructureRef for a different type, will not add to reconciliation request.") + continue } if m.Spec.InfrastructureRef.Name == "" { log.V(4).Info("Machine has an InfrastructureRef with an empty name, will not add to reconciliation request.") + continue } log.WithValues("cloudStackMachine", klog.KRef(m.Spec.InfrastructureRef.Namespace, m.Spec.InfrastructureRef.Name)) @@ -178,6 +187,7 @@ func CloudStackClusterToCloudStackIsolatedNetworks(c client.Client, obj client.O csCluster, ok := o.(*infrav1.CloudStackCluster) if !ok { log.Error(fmt.Errorf("expected a CloudStackCluster but got a %T", o), "Error in CloudStackClusterToCloudStackIsolatedNetworks") + return nil } @@ -186,12 +196,14 @@ func CloudStackClusterToCloudStackIsolatedNetworks(c client.Client, obj client.O // Don't handle deleted CloudStackClusters if !csCluster.ObjectMeta.DeletionTimestamp.IsZero() { log.V(4).Info("CloudStackCluster has a deletion timestamp, skipping mapping.") + return nil } clusterName, err := GetOwnerClusterName(csCluster.ObjectMeta) if err != nil { log.Error(err, "Failed to get owning cluster, skipping mapping.") + return nil } @@ -225,6 +237,7 @@ func CloudStackIsolatedNetworkToControlPlaneCloudStackMachines(c client.Client, csIsoNet, ok := o.(*infrav1.CloudStackIsolatedNetwork) if !ok { log.Error(fmt.Errorf("expected a CloudStackIsolatedNetwork but got a %T", o), "Error in CloudStackIsolatedNetworkToControlPlaneCloudStackMachines") + return nil } @@ -233,6 +246,7 @@ func CloudStackIsolatedNetworkToControlPlaneCloudStackMachines(c client.Client, // Don't handle deleted CloudStackIsolatedNetworks if !csIsoNet.ObjectMeta.DeletionTimestamp.IsZero() { log.V(4).Info("CloudStackIsolatedNetwork has a deletion timestamp, skipping mapping.") + return nil } @@ -249,6 +263,7 @@ func CloudStackIsolatedNetworkToControlPlaneCloudStackMachines(c client.Client, }) if err != nil { log.Error(err, "Failed to get owned control plane Machines, skipping mapping.") + return nil } @@ -260,10 +275,12 @@ func CloudStackIsolatedNetworkToControlPlaneCloudStackMachines(c client.Client, log.WithValues("machine", klog.KObj(&m)) if m.Spec.InfrastructureRef.GroupVersionKind().Kind != "CloudStackMachine" { log.V(4).Info("Machine has an InfrastructureRef for a different type, will not add to reconciliation request.") + continue } if m.Spec.InfrastructureRef.Name == "" { log.V(4).Info("Machine has an InfrastructureRef with an empty name, will not add to reconciliation request.") + continue } log.WithValues("cloudStackMachine", klog.KRef(m.Spec.InfrastructureRef.Namespace, m.Spec.InfrastructureRef.Name)) @@ -275,7 +292,7 @@ func CloudStackIsolatedNetworkToControlPlaneCloudStackMachines(c client.Client, } } -// DebugPredicate returns a predicate that logs the event that triggered the reconciliation +// DebugPredicate returns a predicate that logs the event that triggered the reconciliation. func DebugPredicate(logger logr.Logger) predicate.Funcs { return predicate.Funcs{ UpdateFunc: func(e event.UpdateEvent) bool { @@ -287,6 +304,7 @@ func DebugPredicate(logger logr.Logger) predicate.Funcs { log.V(4).Error(err, "error generating diff") } log.V(4).Info("Update diff", "diff", string(diff), "obj", obj, "kind", e.ObjectOld.GetObjectKind().GroupVersionKind().Kind) + return true }, CreateFunc: func(e event.CreateEvent) bool { @@ -297,6 +315,7 @@ func DebugPredicate(logger logr.Logger) predicate.Funcs { if e.Object.GetObjectKind().GroupVersionKind().Kind == "" { log.V(4).Info("Kind is empty. Here's the whole object", "object", e.Object) } + return true }, DeleteFunc: func(e event.DeleteEvent) bool { @@ -304,6 +323,7 @@ func DebugPredicate(logger logr.Logger) predicate.Funcs { obj := fmt.Sprintf("%s/%s", e.Object.GetNamespace(), e.Object.GetName()) log.V(4).Info("Delete", "obj", obj, "kind", e.Object.GetObjectKind().GroupVersionKind().Kind) + return true }, GenericFunc: func(e event.GenericEvent) bool { @@ -311,6 +331,7 @@ func DebugPredicate(logger logr.Logger) predicate.Funcs { obj := fmt.Sprintf("%s/%s", e.Object.GetNamespace(), e.Object.GetName()) log.V(4).Info("Delete", "obj", obj, "kind", e.Object.GetObjectKind().GroupVersionKind().Kind) + return true }, } diff --git a/main.go b/main.go index 760ba617..9952f382 100644 --- a/main.go +++ b/main.go @@ -20,26 +20,23 @@ import ( "context" "flag" "fmt" - "k8s.io/klog/v2" "net/http" "os" - "sigs.k8s.io/cluster-api-provider-cloudstack/version" "time" "github.com/spf13/pflag" - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" - corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" _ "k8s.io/client-go/plugin/pkg/client/auth" cgrecord "k8s.io/client-go/tools/record" cliflag "k8s.io/component-base/cli/flag" "k8s.io/component-base/logs" logsv1 "k8s.io/component-base/logs/api/v1" + "k8s.io/klog/v2" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/util/flags" - - "k8s.io/apimachinery/pkg/runtime" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" @@ -53,7 +50,7 @@ import ( infrav1b3 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" "sigs.k8s.io/cluster-api-provider-cloudstack/controllers" "sigs.k8s.io/cluster-api-provider-cloudstack/controllers/utils" - //+kubebuilder:scaffold:imports + "sigs.k8s.io/cluster-api-provider-cloudstack/version" ) var ( diff --git a/pkg/cloud/affinity_groups.go b/pkg/cloud/affinity_groups.go index 8ceb546c..40cca881 100644 --- a/pkg/cloud/affinity_groups.go +++ b/pkg/cloud/affinity_groups.go @@ -18,6 +18,7 @@ package cloud import ( "github.com/pkg/errors" + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) @@ -35,19 +36,20 @@ type AffinityGroup struct { } type AffinityGroupIface interface { - FetchAffinityGroup(*AffinityGroup) error - GetOrCreateAffinityGroup(*AffinityGroup) error - DeleteAffinityGroup(*AffinityGroup) error - AssociateAffinityGroup(*infrav1.CloudStackMachine, AffinityGroup) error - DisassociateAffinityGroup(*infrav1.CloudStackMachine, AffinityGroup) error + FetchAffinityGroup(group *AffinityGroup) error + GetOrCreateAffinityGroup(group *AffinityGroup) error + DeleteAffinityGroup(group *AffinityGroup) error + AssociateAffinityGroup(csMachine *infrav1.CloudStackMachine, group AffinityGroup) error + DisassociateAffinityGroup(csMachine *infrav1.CloudStackMachine, group AffinityGroup) error } -func (c *client) FetchAffinityGroup(group *AffinityGroup) (reterr error) { +func (c *client) FetchAffinityGroup(group *AffinityGroup) error { if group.ID != "" { affinityGroup, count, err := c.cs.AffinityGroup.GetAffinityGroupByID(group.ID) if err != nil { // handle via multierr c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return err } else if count > 1 { // handle via creating a new error. @@ -55,6 +57,7 @@ func (c *client) FetchAffinityGroup(group *AffinityGroup) (reterr error) { } else { group.Name = affinityGroup.Name group.Type = affinityGroup.Type + return nil } } @@ -63,6 +66,7 @@ func (c *client) FetchAffinityGroup(group *AffinityGroup) (reterr error) { if err != nil { // handle via multierr c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return err } else if count > 1 { // handle via creating a new error. @@ -70,33 +74,38 @@ func (c *client) FetchAffinityGroup(group *AffinityGroup) (reterr error) { } else { group.ID = affinityGroup.Id group.Type = affinityGroup.Type + return nil } } + return errors.Errorf(`could not fetch AffinityGroup by name "%s" or id "%s"`, group.Name, group.ID) } -func (c *client) GetOrCreateAffinityGroup(group *AffinityGroup) (retErr error) { +func (c *client) GetOrCreateAffinityGroup(group *AffinityGroup) error { if err := c.FetchAffinityGroup(group); err != nil { // Group not found? p := c.cs.AffinityGroup.NewCreateAffinityGroupParams(group.Name, group.Type) p.SetName(group.Name) resp, err := c.cs.AffinityGroup.CreateAffinityGroup(p) if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return err } group.ID = resp.Id } + return nil } -func (c *client) DeleteAffinityGroup(group *AffinityGroup) (retErr error) { +func (c *client) DeleteAffinityGroup(group *AffinityGroup) error { p := c.cs.AffinityGroup.NewDeleteAffinityGroupParams() setIfNotEmpty(group.ID, p.SetId) setIfNotEmpty(group.Name, p.SetName) - _, retErr = c.cs.AffinityGroup.DeleteAffinityGroup(p) - c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(retErr) - return retErr + _, err := c.cs.AffinityGroup.DeleteAffinityGroup(p) + c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + + return err } type affinityGroups []AffinityGroup @@ -105,6 +114,7 @@ func (c *client) getCurrentAffinityGroups(csMachine *infrav1.CloudStackMachine) // Start by fetching VM details which includes an array of currently associated affinity groups. if virtM, count, err := c.cs.VirtualMachine.GetVirtualMachineByID(*csMachine.Spec.InstanceID); err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return nil, err } else if count > 1 { return nil, errors.Errorf("found more than one VM for ID: %s", *csMachine.Spec.InstanceID) @@ -113,6 +123,7 @@ func (c *client) getCurrentAffinityGroups(csMachine *infrav1.CloudStackMachine) for _, v := range virtM.Affinitygroup { groups = append(groups, AffinityGroup{Name: v.Name, Type: v.Type, ID: v.Id}) } + return groups, nil } } @@ -122,6 +133,7 @@ func (ags *affinityGroups) toArrayOfIDs() []string { for _, group := range *ags { groupIDs = append(groupIDs, group.ID) } + return groupIDs } @@ -150,41 +162,46 @@ func (ags *affinityGroups) removeGroup(removeGroup AffinityGroup) { } } -func (c *client) stopAndModifyAffinityGroups(csMachine *infrav1.CloudStackMachine, groups affinityGroups) (retErr error) { +func (c *client) stopAndModifyAffinityGroups(csMachine *infrav1.CloudStackMachine, groups affinityGroups) error { agp := c.cs.AffinityGroup.NewUpdateVMAffinityGroupParams(*csMachine.Spec.InstanceID) agp.SetAffinitygroupids(groups.toArrayOfIDs()) - p1 := c.cs.VirtualMachine.NewStopVirtualMachineParams(string(*csMachine.Spec.InstanceID)) + p1 := c.cs.VirtualMachine.NewStopVirtualMachineParams(*csMachine.Spec.InstanceID) if _, err := c.cs.VirtualMachine.StopVirtualMachine(p1); err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return err } if _, err := c.cs.AffinityGroup.UpdateVMAffinityGroup(agp); err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return err } - p2 := c.cs.VirtualMachine.NewStartVirtualMachineParams(string(*csMachine.Spec.InstanceID)) + p2 := c.cs.VirtualMachine.NewStartVirtualMachineParams(*csMachine.Spec.InstanceID) _, err := c.cs.VirtualMachine.StartVirtualMachine(p2) c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return err } -func (c *client) AssociateAffinityGroup(csMachine *infrav1.CloudStackMachine, group AffinityGroup) (retErr error) { +func (c *client) AssociateAffinityGroup(csMachine *infrav1.CloudStackMachine, group AffinityGroup) error { groups, err := c.getCurrentAffinityGroups(csMachine) if err != nil { return err } groups.addGroup(group) + return c.stopAndModifyAffinityGroups(csMachine, groups) } -func (c *client) DisassociateAffinityGroup(csMachine *infrav1.CloudStackMachine, group AffinityGroup) (retErr error) { +func (c *client) DisassociateAffinityGroup(csMachine *infrav1.CloudStackMachine, group AffinityGroup) error { groups, err := c.getCurrentAffinityGroups(csMachine) if err != nil { return err } groups.removeGroup(group) + return c.stopAndModifyAffinityGroups(csMachine, groups) } diff --git a/pkg/cloud/affinity_groups_test.go b/pkg/cloud/affinity_groups_test.go index 11738874..3310f601 100644 --- a/pkg/cloud/affinity_groups_test.go +++ b/pkg/cloud/affinity_groups_test.go @@ -23,6 +23,7 @@ import ( "github.com/golang/mock/gomock" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" ) @@ -131,7 +132,6 @@ var _ = Describe("AffinityGroup Unit Tests", func() { }) Context("AffinityGroup Integ Tests", Label("integ"), func() { - BeforeEach(func() { client = realCloudClient dummies.AffinityGroup.ID = "" // Force name fetching. diff --git a/pkg/cloud/client.go b/pkg/cloud/client.go index 804a6950..d57c1134 100644 --- a/pkg/cloud/client.go +++ b/pkg/cloud/client.go @@ -24,14 +24,13 @@ import ( "sync" "time" - corev1 "k8s.io/api/core/v1" - - "gopkg.in/yaml.v3" - "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/metrics" - "github.com/apache/cloudstack-go/v2/cloudstack" "github.com/jellydator/ttlcache/v3" "github.com/pkg/errors" + "gopkg.in/yaml.v3" + corev1 "k8s.io/api/core/v1" + + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/metrics" ) //go:generate ../../hack/tools/bin/mockgen -destination=../mocks/mock_client.go -package=mocks sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud Client @@ -44,7 +43,7 @@ type Client interface { ZoneIFace IsoNetworkIface UserCredIFace - NewClientInDomainAndAccount(string, string) (Client, error) + NewClientInDomainAndAccount(domain string, account string) (Client, error) } // cloud-config ini structure. @@ -71,16 +70,22 @@ type SecretConfig struct { StringData Config `yaml:"stringData"` } -var clientCache *ttlcache.Cache[string, *client] -var cacheMutex sync.Mutex +var ( + clientCache *ttlcache.Cache[string, *client] + cacheMutex sync.Mutex +) -var NewAsyncClient = cloudstack.NewAsyncClient -var NewClient = cloudstack.NewClient +var ( + NewAsyncClient = cloudstack.NewAsyncClient + NewClient = cloudstack.NewClient +) -const ClientConfigMapName = "capc-client-config" -const ClientConfigMapNamespace = "capc-system" -const ClientCacheTTLKey = "client-cache-ttl" -const DefaultClientCacheTTL = time.Duration(1 * time.Hour) +const ( + ClientConfigMapName = "capc-client-config" + ClientConfigMapNamespace = "capc-system" + ClientCacheTTLKey = "client-cache-ttl" + DefaultClientCacheTTL = 1 * time.Hour +) // UnmarshalAllSecretConfigs parses a yaml document for each secret. func UnmarshalAllSecretConfigs(in []byte, out *[]SecretConfig) error { @@ -90,17 +95,19 @@ func UnmarshalAllSecretConfigs(in []byte, out *[]SecretConfig) error { var conf SecretConfig if err := decoder.Decode(&conf); err != nil { // Break when there are no more documents to decode - if err != io.EOF { + if errors.Is(err, io.EOF) { return err } + break } *out = append(*out, conf) } + return nil } -// NewClientFromK8sSecret returns a client from a k8s secret +// NewClientFromK8sSecret returns a client from a k8s secret. func NewClientFromK8sSecret(endpointSecret *corev1.Secret, clientConfig *corev1.ConfigMap) (Client, error) { endpointSecretStrings := map[string]string{} for k, v := range endpointSecret.Data { @@ -110,6 +117,7 @@ func NewClientFromK8sSecret(endpointSecret *corev1.Secret, clientConfig *corev1. if err != nil { return nil, err } + return NewClientFromBytesConfig(bytes, clientConfig) } @@ -139,6 +147,7 @@ func NewClientFromYamlPath(confPath string, secretName string) (Client, error) { for _, config := range *configs { if config.Metadata["name"] == secretName { conf = config.StringData + break } } @@ -242,15 +251,16 @@ func NewClientFromCSAPIClient(cs *cloudstack.CloudStackClient, user *User) Clien customMetrics: metrics.NewCustomMetrics(), user: user, } + return c } -// generateClientCacheKey generates a cache key from a Config +// generateClientCacheKey generates a cache key from a Config. func generateClientCacheKey(conf Config) string { return fmt.Sprintf("%+v", conf) } -// newClientCache returns a new instance of client cache +// newClientCache returns a new instance of client cache. func newClientCache(clientConfig *corev1.ConfigMap) *ttlcache.Cache[string, *client] { cache := ttlcache.New[string, *client]( ttlcache.WithTTL[string, *client](GetClientCacheTTL(clientConfig)), @@ -262,7 +272,7 @@ func newClientCache(clientConfig *corev1.ConfigMap) *ttlcache.Cache[string, *cli return cache } -// GetClientCacheTTL returns a client cache TTL duration from the passed config map +// GetClientCacheTTL returns a client cache TTL duration from the passed config map. func GetClientCacheTTL(clientConfig *corev1.ConfigMap) time.Duration { var cacheTTL time.Duration if clientConfig != nil { @@ -273,5 +283,6 @@ func GetClientCacheTTL(clientConfig *corev1.ConfigMap) time.Duration { if cacheTTL == 0 { cacheTTL = DefaultClientCacheTTL } + return cacheTTL } diff --git a/pkg/cloud/client_test.go b/pkg/cloud/client_test.go index 74377110..5dc72034 100644 --- a/pkg/cloud/client_test.go +++ b/pkg/cloud/client_test.go @@ -20,19 +20,18 @@ import ( "os" "time" - corev1 "k8s.io/api/core/v1" - "github.com/apache/cloudstack-go/v2/cloudstack" "github.com/golang/mock/gomock" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta1" "sigs.k8s.io/cluster-api-provider-cloudstack/test/helpers" ) var _ = Describe("Client", func() { - var ( mockCtrl *gomock.Controller mockClient *cloudstack.CloudStackClient @@ -59,12 +58,12 @@ var _ = Describe("Client", func() { // abilities to parse said schema. Skip("Dev test suite.") // Create a real cloud client. - var connectionErr error - _, connectionErr = helpers.NewCSClient() - Ω(connectionErr).ShouldNot(HaveOccurred()) + var errConnect error + _, errConnect = helpers.NewCSClient() + Ω(errConnect).ShouldNot(HaveOccurred()) - _, connectionErr = cloud.NewClientFromYamlPath(os.Getenv("REPO_ROOT")+"/cloud-config.yaml", "myendpoint") - Ω(connectionErr).ShouldNot(HaveOccurred()) + _, errConnect = cloud.NewClientFromYamlPath(os.Getenv("REPO_ROOT")+"/cloud-config.yaml", "myendpoint") + Ω(errConnect).ShouldNot(HaveOccurred()) }) }) @@ -149,7 +148,6 @@ var _ = Describe("Client", func() { Apikey: dummies.Apikey, Secretkey: dummies.SecretKey, }, nil).AnyTimes() - }) It("Returns a new client", func() { diff --git a/pkg/cloud/cloud_suite_test.go b/pkg/cloud/cloud_suite_test.go index 0140c696..7f99e579 100644 --- a/pkg/cloud/cloud_suite_test.go +++ b/pkg/cloud/cloud_suite_test.go @@ -25,6 +25,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/util/uuid" + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" "sigs.k8s.io/cluster-api-provider-cloudstack/test/helpers" @@ -61,7 +62,8 @@ func TestCloud(t *testing.T) { uid := string(uuid.NewUUID()) newAccount := cloud.Account{ Name: "TestAccount-" + uid, - Domain: cloud.Domain{Name: "TestDomain-" + uid, Path: "ROOT/TestDomain-" + uid}} + Domain: cloud.Domain{Name: "TestDomain-" + uid, Path: "ROOT/TestDomain-" + uid}, + } newUser := cloud.User{Account: newAccount} Ω(helpers.GetOrCreateUserWithKey(realCSClient, &newUser)).Should(Succeed()) testDomainPath = newAccount.Domain.Path diff --git a/pkg/cloud/helpers.go b/pkg/cloud/helpers.go index 44541f15..55ae0443 100644 --- a/pkg/cloud/helpers.go +++ b/pkg/cloud/helpers.go @@ -21,9 +21,11 @@ import ( cgzip "compress/gzip" ) -type set func(string) -type setArray func([]string) -type setInt func(int64) +type ( + set func(string) + setArray func([]string) + setInt func(int64) +) func setIfNotEmpty(str string, setFn set) { if str != "" { @@ -52,5 +54,6 @@ func compress(str string) (string, error) { if err := w.Close(); err != nil { return "", err } + return buf.String(), nil } diff --git a/pkg/cloud/helpers_test.go b/pkg/cloud/helpers_test.go index 78fcdcf2..1afd1e45 100644 --- a/pkg/cloud/helpers_test.go +++ b/pkg/cloud/helpers_test.go @@ -19,7 +19,6 @@ package cloud_test import ( "bytes" "compress/gzip" - "fmt" "io" "reflect" @@ -47,7 +46,7 @@ func (p paramMatcher) String() string { return "a gomega matcher to match, and said matcher should have panicked before this message was printed." } -func (p paramMatcher) Matches(x interface{}) (retVal bool) { +func (p paramMatcher) Matches(x interface{}) bool { return Ω(x).Should(p.matcher) } @@ -67,7 +66,6 @@ func FieldMatcherGenerator(fetchFunc string) func(string) types.GomegaMatcher { return WithTransform( func(x interface{}) string { meth := reflect.ValueOf(x).MethodByName(fetchFunc) - fmt.Println(meth.Call(nil)[0]) return meth.Call(nil)[0].String() }, Equal(expected)) @@ -85,5 +83,6 @@ func decompress(data []byte) ([]byte, error) { if err != nil { return nil, err } + return data, nil } diff --git a/pkg/cloud/instance.go b/pkg/cloud/instance.go index b6de460e..ec1cb3be 100644 --- a/pkg/cloud/instance.go +++ b/pkg/cloud/instance.go @@ -22,27 +22,42 @@ import ( "strconv" "strings" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" - "github.com/apache/cloudstack-go/v2/cloudstack" "github.com/hashicorp/go-multierror" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) +const ( + // State of the virtual machine. Possible values are: Running, Stopped, Present, Destroyed, Expunged. + // Present is used for the state equal not destroyed. + VMStateRunning = "Running" + VMStateStopped = "Stopped" + VMStatePresent = "Present" + VMStateDestroyed = "Destroyed" + VMStateExpunged = "Expunged" + VMStateError = "Error" + + // LimitUnlimited is used in account/domain limit checks. + LimitUnlimited = "Unlimited" +) + +var ErrNotFound = errors.New("not found") + type VMIface interface { - GetOrCreateVMInstance(*infrav1.CloudStackMachine, *clusterv1.Machine, *infrav1.CloudStackFailureDomain, *infrav1.CloudStackAffinityGroup, string) error - ResolveVMInstanceDetails(*infrav1.CloudStackMachine) error - DestroyVMInstance(*infrav1.CloudStackMachine) error + GetOrCreateVMInstance(csMachine *infrav1.CloudStackMachine, capiMachine *clusterv1.Machine, fd *infrav1.CloudStackFailureDomain, affinity *infrav1.CloudStackAffinityGroup, userData string) error + ResolveVMInstanceDetails(csMachine *infrav1.CloudStackMachine) error + DestroyVMInstance(csMachine *infrav1.CloudStackMachine) error } // Set infrastructure spec and status from the CloudStack API's virtual machine metrics type. func setMachineDataFromVMMetrics(vmResponse *cloudstack.VirtualMachinesMetric, csMachine *infrav1.CloudStackMachine) { - csMachine.Spec.ProviderID = pointer.String(fmt.Sprintf("cloudstack:///%s", vmResponse.Id)) + csMachine.Spec.ProviderID = pointer.String("cloudstack:///" + vmResponse.Id) // InstanceID is later used as required parameter to destroy VM. csMachine.Spec.InstanceID = pointer.String(vmResponse.Id) csMachine.Status.Addresses = []corev1.NodeAddress{{Type: corev1.NodeInternalIP, Address: vmResponse.Ipaddress}} @@ -61,11 +76,13 @@ func (c *client) ResolveVMInstanceDetails(csMachine *infrav1.CloudStackMachine) vmResp, count, err := c.cs.VirtualMachine.GetVirtualMachinesMetricByID(*csMachine.Spec.InstanceID) if err != nil && !strings.Contains(strings.ToLower(err.Error()), "no match found") { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return err } else if count > 1 { return fmt.Errorf("found more than one VM Instance with ID %s", *csMachine.Spec.InstanceID) } else if err == nil { setMachineDataFromVMMetrics(vmResp, csMachine) + return nil } } @@ -75,48 +92,57 @@ func (c *client) ResolveVMInstanceDetails(csMachine *infrav1.CloudStackMachine) vmResp, count, err := c.cs.VirtualMachine.GetVirtualMachinesMetricByName(csMachine.Name) // add opts usage if err != nil && !strings.Contains(strings.ToLower(err.Error()), "no match") { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return err } else if count > 1 { return fmt.Errorf("found more than one VM Instance with name %s", csMachine.Name) } else if err == nil { setMachineDataFromVMMetrics(vmResp, csMachine) + return nil } } + return errors.New("no match found") } -func (c *client) ResolveServiceOffering(csMachine *infrav1.CloudStackMachine, zoneID string) (offering cloudstack.ServiceOffering, retErr error) { +// resolveServiceOffering attempts to look up the service offering of a CloudStackMachine by ID first and name second. +func (c *client) resolveServiceOffering(csMachine *infrav1.CloudStackMachine, zoneID string) (offering *cloudstack.ServiceOffering, retErr error) { if len(csMachine.Spec.Offering.ID) > 0 { csOffering, count, err := c.cs.ServiceOffering.GetServiceOfferingByID(csMachine.Spec.Offering.ID) if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) - return cloudstack.ServiceOffering{}, multierror.Append(retErr, errors.Wrapf( + + return nil, multierror.Append(retErr, errors.Wrapf( err, "could not get Service Offering by ID %s", csMachine.Spec.Offering.ID)) } else if count != 1 { - return *csOffering, multierror.Append(retErr, errors.Errorf( + return csOffering, multierror.Append(retErr, errors.Errorf( "expected 1 Service Offering with UUID %s, but got %d", csMachine.Spec.Offering.ID, count)) } if len(csMachine.Spec.Offering.Name) > 0 && csMachine.Spec.Offering.Name != csOffering.Name { - return *csOffering, multierror.Append(retErr, errors.Errorf( + return csOffering, multierror.Append(retErr, errors.Errorf( "offering name %s does not match name %s returned using UUID %s", csMachine.Spec.Offering.Name, csOffering.Name, csMachine.Spec.Offering.ID)) } - return *csOffering, nil + + return csOffering, nil } csOffering, count, err := c.cs.ServiceOffering.GetServiceOfferingByName(csMachine.Spec.Offering.Name, cloudstack.WithZone(zoneID)) if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) - return cloudstack.ServiceOffering{}, multierror.Append(retErr, errors.Wrapf( + + return nil, multierror.Append(retErr, errors.Wrapf( err, "could not get Service Offering ID from %s in zone %s", csMachine.Spec.Offering.Name, zoneID)) } else if count != 1 { - return *csOffering, multierror.Append(retErr, errors.Errorf( + return csOffering, multierror.Append(retErr, errors.Errorf( "expected 1 Service Offering with name %s in zone %s, but got %d", csMachine.Spec.Offering.Name, zoneID, count)) } - return *csOffering, nil + + return csOffering, nil } -func (c *client) ResolveTemplate( +// resolveTemplate attempts to look up/verify the template ID of a CloudStackMachine by ID first and name second. +func (c *client) resolveTemplate( csMachine *infrav1.CloudStackMachine, zoneID string, ) (templateID string, retErr error) { @@ -124,6 +150,7 @@ func (c *client) ResolveTemplate( csTemplate, count, err := c.cs.Template.GetTemplateByID(csMachine.Spec.Template.ID, "executable") if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return "", multierror.Append(retErr, errors.Wrapf( err, "could not get Template by ID %s", csMachine.Spec.Template.ID)) } else if count != 1 { @@ -135,6 +162,7 @@ func (c *client) ResolveTemplate( return "", multierror.Append(retErr, errors.Errorf( "template name %s does not match name %s returned using UUID %s", csMachine.Spec.Template.Name, csTemplate.Name, csMachine.Spec.Template.ID)) } + return csMachine.Spec.Template.ID, nil } templateID, count, err := c.cs.Template.GetTemplateID(csMachine.Spec.Template.Name, "executable", zoneID, func(cs *cloudstack.CloudStackClient, i interface{}) error { @@ -148,19 +176,21 @@ func (c *client) ResolveTemplate( }) if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return "", multierror.Append(retErr, errors.Wrapf( err, "could not get Template ID from %s", csMachine.Spec.Template.Name)) } else if count != 1 { return "", multierror.Append(retErr, errors.Errorf( "expected 1 Template with name %s, but got %d", csMachine.Spec.Template.Name, count)) } + return templateID, nil } -// ResolveDiskOffering Retrieves diskOffering by using disk offering ID if ID is provided and confirm returned -// disk offering name matches name provided in spec. -// If disk offering ID is not provided, the disk offering name is used to retrieve disk offering ID. -func (c *client) ResolveDiskOffering(csMachine *infrav1.CloudStackMachine, zoneID string) (diskOfferingID string, retErr error) { +// resolveDiskOffering retrieves a diskOffering by using disk offering ID if ID is provided, and checks if the returned +// disk offering name matches the name provided in the machine spec. +// If disk offering ID is not provided, the disk offering name is used to retrieve the disk offering ID. +func (c *client) resolveDiskOffering(csMachine *infrav1.CloudStackMachine, zoneID string) (diskOfferingID string, retErr error) { if csMachine.Spec.DiskOffering == nil { return "", nil } @@ -169,6 +199,7 @@ func (c *client) ResolveDiskOffering(csMachine *infrav1.CloudStackMachine, zoneI diskID, count, err := c.cs.DiskOffering.GetDiskOfferingID(csMachine.Spec.DiskOffering.Name, cloudstack.WithZone(zoneID)) if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return "", multierror.Append(retErr, errors.Wrapf( err, "could not get DiskOffering ID from %s", csMachine.Spec.DiskOffering.Name)) } else if count != 1 { @@ -196,6 +227,7 @@ func verifyDiskoffering(csMachine *infrav1.CloudStackMachine, c *client, diskOff csDiskOffering, count, err := c.cs.DiskOffering.GetDiskOfferingByID(diskOfferingID) if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return "", multierror.Append(retErr, errors.Wrapf( err, "could not get DiskOffering by ID %s", diskOfferingID)) } else if count != 1 { @@ -214,69 +246,76 @@ func verifyDiskoffering(csMachine *infrav1.CloudStackMachine, c *client, diskOff "diskOffering with UUID %s is not customized, disk size can not be specified", diskOfferingID)) } + return diskOfferingID, nil } -// CheckAccountLimits Checks the account's limit of VM, CPU & Memory -func (c *client) CheckAccountLimits(offering *cloudstack.ServiceOffering) error { - if c.user.Account.CPUAvailable != "Unlimited" { +// checkAccountLimits checks the account's limit of VM, CPU & Memory. +func (c *client) checkAccountLimits(offering *cloudstack.ServiceOffering) error { + if c.user.Account.CPUAvailable != LimitUnlimited { cpuAvailable, err := strconv.ParseInt(c.user.Account.CPUAvailable, 10, 0) if err == nil && int64(offering.Cpunumber) > cpuAvailable { return fmt.Errorf("CPU available (%d) in account can't fulfil the requirement: %d", cpuAvailable, offering.Cpunumber) } } - if c.user.Account.MemoryAvailable != "Unlimited" { + if c.user.Account.MemoryAvailable != LimitUnlimited { memoryAvailable, err := strconv.ParseInt(c.user.Account.MemoryAvailable, 10, 0) if err == nil && int64(offering.Memory) > memoryAvailable { return fmt.Errorf("memory available (%d) in account can't fulfil the requirement: %d", memoryAvailable, offering.Memory) } } - if c.user.Account.VMAvailable != "Unlimited" { + if c.user.Account.VMAvailable != LimitUnlimited { vmAvailable, err := strconv.ParseInt(c.user.Account.VMAvailable, 10, 0) if err == nil && vmAvailable < 1 { - return fmt.Errorf("VM Limit in account has reached it's maximum value") + return errors.New("VM limit in account has reached its maximum value") } } + return nil } -// CheckDomainLimits Checks the domain's limit of VM, CPU & Memory -func (c *client) CheckDomainLimits(offering *cloudstack.ServiceOffering) error { - if c.user.Account.Domain.CPUAvailable != "Unlimited" { +// checkDomainLimits checks the domain's limit of VM, CPU & Memory. +func (c *client) checkDomainLimits(offering *cloudstack.ServiceOffering) error { + if c.user.Account.Domain.CPUAvailable != LimitUnlimited { cpuAvailable, err := strconv.ParseInt(c.user.Account.Domain.CPUAvailable, 10, 0) if err == nil && int64(offering.Cpunumber) > cpuAvailable { return fmt.Errorf("CPU available (%d) in domain can't fulfil the requirement: %d", cpuAvailable, offering.Cpunumber) } } - if c.user.Account.Domain.MemoryAvailable != "Unlimited" { + if c.user.Account.Domain.MemoryAvailable != LimitUnlimited { memoryAvailable, err := strconv.ParseInt(c.user.Account.Domain.MemoryAvailable, 10, 0) if err == nil && int64(offering.Memory) > memoryAvailable { return fmt.Errorf("memory available (%d) in domain can't fulfil the requirement: %d", memoryAvailable, offering.Memory) } } - if c.user.Account.Domain.VMAvailable != "Unlimited" { + if c.user.Account.Domain.VMAvailable != LimitUnlimited { vmAvailable, err := strconv.ParseInt(c.user.Account.Domain.VMAvailable, 10, 0) if err == nil && vmAvailable < 1 { - return fmt.Errorf("VM Limit in domain has reached it's maximum value") + return errors.New("VM limit in domain has reached its maximum value") } } + return nil } -// CheckLimits will check the account & domain limits -func (c *client) CheckLimits( +// checkLimits will check the account & domain limits. +func (c *client) checkLimits( offering *cloudstack.ServiceOffering, ) error { - err := c.CheckAccountLimits(offering) + if offering == nil { + return errors.New("offering cannot be nil") + } + + err := c.checkAccountLimits(offering) if err != nil { return err } - err = c.CheckDomainLimits(offering) + err = c.checkDomainLimits(offering) if err != nil { return err } @@ -284,9 +323,8 @@ func (c *client) CheckLimits( return nil } -// DeployVM will create a VM instance, -// and sets the infrastructure machine spec and status accordingly. -func (c *client) DeployVM( +// deployVM will create a VM instance, and sets the infrastructure machine spec and status accordingly. +func (c *client) deployVM( csMachine *infrav1.CloudStackMachine, capiMachine *clusterv1.Machine, fd *infrav1.CloudStackFailureDomain, @@ -294,11 +332,11 @@ func (c *client) DeployVM( offering *cloudstack.ServiceOffering, userData string, ) error { - templateID, err := c.ResolveTemplate(csMachine, fd.Spec.Zone.ID) + templateID, err := c.resolveTemplate(csMachine, fd.Spec.Zone.ID) if err != nil { return err } - diskOfferingID, err := c.ResolveDiskOffering(csMachine, fd.Spec.Zone.ID) + diskOfferingID, err := c.resolveDiskOffering(csMachine, fd.Spec.Zone.ID) if err != nil { return err } @@ -325,7 +363,7 @@ func (c *client) DeployVM( if len(csMachine.Spec.AffinityGroupIDs) > 0 { p.SetAffinitygroupids(csMachine.Spec.AffinityGroupIDs) - } else if strings.ToLower(csMachine.Spec.Affinity) != "no" && csMachine.Spec.Affinity != "" { + } else if !strings.EqualFold(csMachine.Spec.Affinity, "no") && csMachine.Spec.Affinity != "" { p.SetAffinitygroupids([]string{affinity.Spec.ID}) } @@ -342,13 +380,14 @@ func (c *client) DeployVM( // clean up. vm, findErr := findVirtualMachine(c.cs.VirtualMachine, templateID, fd, csMachine) if findErr != nil { - c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(findErr) - return fmt.Errorf("%v; find virtual machine: %v", err, findErr) - } - - // We didn't find a VM so return the original error. - if vm == nil { - return err + if errors.Is(findErr, ErrNotFound) { + // We didn't find a VM so return the original error. + return err + } else { + c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(findErr) + + return fmt.Errorf("%w; find virtual machine: %w", err, findErr) + } } csMachine.Spec.InstanceID = pointer.String(vm.Id) @@ -363,8 +402,8 @@ func (c *client) DeployVM( return nil } -// GetOrCreateVMInstance CreateVMInstance will fetch or create a VM instance, and -// sets the infrastructure machine spec and status accordingly. +// GetOrCreateVMInstance will fetch or create a VM instance, and sets the infrastructure machine spec +// and status accordingly. func (c *client) GetOrCreateVMInstance( csMachine *infrav1.CloudStackMachine, capiMachine *clusterv1.Machine, @@ -378,27 +417,27 @@ func (c *client) GetOrCreateVMInstance( return err } - offering, err := c.ResolveServiceOffering(csMachine, fd.Spec.Zone.ID) + offering, err := c.resolveServiceOffering(csMachine, fd.Spec.Zone.ID) if err != nil { return err } - err = c.CheckLimits(&offering) + err = c.checkLimits(offering) if err != nil { return err } - if err := c.DeployVM(csMachine, capiMachine, fd, affinity, &offering, userData); err != nil { + if err := c.deployVM(csMachine, capiMachine, fd, affinity, offering, userData); err != nil { return err } - // Resolve uses a VM metrics request response to fill cloudstack machine status. + // ResolveVMInstanceDetails uses a VM metrics request response to fill the CloudStack machine status. // The deployment response is insufficient. return c.ResolveVMInstanceDetails(csMachine) } // findVirtualMachine retrieves a virtual machine by matching its expected name, template, failure -// domain zone and failure domain network. If no virtual machine is found it returns nil, nil. +// domain zone and failure domain network. If no virtual machine is found it returns nil, ErrNotFound. func findVirtualMachine( client cloudstack.VirtualMachineServiceIface, templateID string, @@ -417,7 +456,7 @@ func findVirtualMachine( } if response.Count == 0 { - return nil, nil + return nil, ErrNotFound } return response.VirtualMachines[0], nil @@ -442,6 +481,7 @@ func (c *client) DestroyVMInstance(csMachine *infrav1.CloudStackMachine) error { return nil } else if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return err } @@ -454,12 +494,15 @@ func (c *client) DestroyVMInstance(csMachine *infrav1.CloudStackMachine) error { // VM doesn't exist. So the desired state is in effect. Our work is done here. return nil } + return err } return errors.New("VM deletion in progress") } +// listVMInstanceDatadiskVolumeIDs fetches a list of any data disks associated with the VM (that were created upon VM +// creation). This tries to exclude any disks that were attached to the VM at a stage other than VM creation. func (c *client) listVMInstanceDatadiskVolumeIDs(instanceID string) ([]string, error) { p := c.cs.Volume.NewListVolumesParams() p.SetVirtualmachineid(instanceID) @@ -477,14 +520,15 @@ func (c *client) listVMInstanceDatadiskVolumeIDs(instanceID string) ([]string, e // - https://github.com/kubernetes-sigs/cluster-api-provider-cloudstack/issues/389 p.SetKeyword("DATA-") - listVOLResp, err := c.csAsync.Volume.ListVolumes(p) + listVolResp, err := c.csAsync.Volume.ListVolumes(p) if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return nil, err } - var ret []string - for _, vol := range listVOLResp.Volumes { + ret := make([]string, 0, len(listVolResp.Volumes)) + for _, vol := range listVolResp.Volumes { ret = append(ret, vol.Id) } diff --git a/pkg/cloud/instance_test.go b/pkg/cloud/instance_test.go index 430c9798..498f5d4b 100644 --- a/pkg/cloud/instance_test.go +++ b/pkg/cloud/instance_test.go @@ -18,17 +18,17 @@ package cloud_test import ( "encoding/base64" - "fmt" + "github.com/apache/cloudstack-go/v2/cloudstack" "github.com/golang/mock/gomock" . "github.com/onsi/ginkgo/v2" - infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" - "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" - dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" - . "github.com/onsi/gomega" "github.com/pkg/errors" "k8s.io/utils/pointer" + + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" + dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" ) var _ = Describe("Instance", func() { @@ -38,6 +38,9 @@ var _ = Describe("Instance", func() { templateFakeID = "456" executableFilter = "executable" diskOfferingFakeID = "789" + + offeringName = "offering" + templateName = "template" ) notFoundError := errors.New("no match found") @@ -113,7 +116,7 @@ var _ = Describe("Instance", func() { Ω(client.ResolveVMInstanceDetails(dummies.CSMachine1)).Should(Succeed()) Ω(dummies.CSMachine1.Spec.ProviderID).Should(Equal( - pointer.String(fmt.Sprintf("cloudstack:///%s", *dummies.CSMachine1.Spec.InstanceID)))) + pointer.String("cloudstack:///" + *dummies.CSMachine1.Spec.InstanceID))) Ω(dummies.CSMachine1.Spec.InstanceID).Should(Equal(pointer.String(*dummies.CSMachine1.Spec.InstanceID))) }) }) @@ -390,7 +393,7 @@ var _ = Describe("Instance", func() { c := cloud.NewClientFromCSAPIClient(mockClient, user) Ω(c.GetOrCreateVMInstance( dummies.CSMachine1, dummies.CAPIMachine, dummies.CSFailureDomain1, dummies.CSAffinityGroup, "")). - Should(MatchError("VM Limit in account has reached it's maximum value")) + Should(MatchError("VM limit in account has reached its maximum value")) }) It("returns errors when there is not enough available VM limit in domain", func() { @@ -418,7 +421,7 @@ var _ = Describe("Instance", func() { c := cloud.NewClientFromCSAPIClient(mockClient, user) Ω(c.GetOrCreateVMInstance( dummies.CSMachine1, dummies.CAPIMachine, dummies.CSFailureDomain1, dummies.CSAffinityGroup, "")). - Should(MatchError("VM Limit in domain has reached it's maximum value")) + Should(MatchError("VM limit in domain has reached its maximum value")) }) }) @@ -470,7 +473,7 @@ var _ = Describe("Instance", func() { func(p interface{}) { params := p.(*cloudstack.DeployVirtualMachineParams) displayName, _ := params.GetDisplayname() - Ω(displayName == dummies.CAPIMachine.Name).Should(BeTrue()) + Ω(displayName).Should(Equal(dummies.CAPIMachine.Name)) b64UserData, _ := params.GetUserdata() @@ -492,8 +495,8 @@ var _ = Describe("Instance", func() { dummies.CSMachine1.Spec.DiskOffering.ID = diskOfferingFakeID dummies.CSMachine1.Spec.Offering.ID = "" dummies.CSMachine1.Spec.Template.ID = "" - dummies.CSMachine1.Spec.Offering.Name = "offering" - dummies.CSMachine1.Spec.Template.Name = "template" + dummies.CSMachine1.Spec.Offering.Name = offeringName + dummies.CSMachine1.Spec.Template.Name = templateName sos.EXPECT().GetServiceOfferingByName(dummies.CSMachine1.Spec.Offering.Name, gomock.Any()).Return(&cloudstack.ServiceOffering{ Id: offeringFakeID, @@ -511,8 +514,8 @@ var _ = Describe("Instance", func() { It("works with service offering name and template name without disk offering", func() { dummies.CSMachine1.Spec.Offering.ID = "" dummies.CSMachine1.Spec.Template.ID = "" - dummies.CSMachine1.Spec.Offering.Name = "offering" - dummies.CSMachine1.Spec.Template.Name = "template" + dummies.CSMachine1.Spec.Offering.Name = offeringName + dummies.CSMachine1.Spec.Template.Name = templateName dummies.CSMachine1.Spec.DiskOffering = &infrav1.CloudStackResourceDiskOffering{} sos.EXPECT().GetServiceOfferingByName(dummies.CSMachine1.Spec.Offering.Name, gomock.Any()).Return(&cloudstack.ServiceOffering{ @@ -531,7 +534,7 @@ var _ = Describe("Instance", func() { dummies.CSMachine1.Spec.Offering.ID = offeringFakeID dummies.CSMachine1.Spec.Template.ID = "" dummies.CSMachine1.Spec.Offering.Name = "" - dummies.CSMachine1.Spec.Template.Name = "template" + dummies.CSMachine1.Spec.Template.Name = templateName sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID).Return(&cloudstack.ServiceOffering{ Id: offeringFakeID, @@ -550,7 +553,7 @@ var _ = Describe("Instance", func() { dummies.CSMachine1.Spec.DiskOffering.ID = diskOfferingFakeID dummies.CSMachine1.Spec.Offering.ID = "" dummies.CSMachine1.Spec.Template.ID = templateFakeID - dummies.CSMachine1.Spec.Offering.Name = "offering" + dummies.CSMachine1.Spec.Offering.Name = offeringName dummies.CSMachine1.Spec.Template.Name = "" sos.EXPECT().GetServiceOfferingByName(dummies.CSMachine1.Spec.Offering.Name, gomock.Any()).Return(&cloudstack.ServiceOffering{ @@ -584,7 +587,7 @@ var _ = Describe("Instance", func() { dos.EXPECT().GetDiskOfferingByID(dummies.CSMachine1.Spec.DiskOffering.ID). Return(&cloudstack.DiskOffering{Iscustomized: false}, 1, nil) ts.EXPECT().GetTemplateByID(dummies.CSMachine1.Spec.Template.ID, executableFilter). - Return(&cloudstack.Template{Name: "template"}, 1, nil) + Return(&cloudstack.Template{Name: templateName}, 1, nil) ActionAndAssert() }) @@ -593,8 +596,8 @@ var _ = Describe("Instance", func() { dummies.CSMachine1.Spec.DiskOffering.ID = diskOfferingFakeID dummies.CSMachine1.Spec.Offering.ID = offeringFakeID dummies.CSMachine1.Spec.Template.ID = templateFakeID - dummies.CSMachine1.Spec.Offering.Name = "offering" - dummies.CSMachine1.Spec.Template.Name = "template" + dummies.CSMachine1.Spec.Offering.Name = offeringName + dummies.CSMachine1.Spec.Template.Name = templateName sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID).Return(&cloudstack.ServiceOffering{ Id: dummies.CSMachine1.Spec.Offering.ID, @@ -602,7 +605,7 @@ var _ = Describe("Instance", func() { Cpunumber: 1, Memory: 1024, }, 1, nil) - ts.EXPECT().GetTemplateByID(dummies.CSMachine1.Spec.Template.ID, executableFilter).Return(&cloudstack.Template{Name: "template"}, 1, nil) + ts.EXPECT().GetTemplateByID(dummies.CSMachine1.Spec.Template.ID, executableFilter).Return(&cloudstack.Template{Name: templateName}, 1, nil) dos.EXPECT().GetDiskOfferingID(dummies.CSMachine1.Spec.DiskOffering.Name, gomock.Any()).Return(diskOfferingFakeID, 1, nil) dos.EXPECT().GetDiskOfferingByID(dummies.CSMachine1.Spec.DiskOffering.ID).Return(&cloudstack.DiskOffering{Iscustomized: false}, 1, nil) @@ -620,8 +623,8 @@ var _ = Describe("Instance", func() { It("works with Id and name both provided, offering name mismatch", func() { dummies.CSMachine1.Spec.Offering.ID = offeringFakeID dummies.CSMachine1.Spec.Template.ID = templateFakeID - dummies.CSMachine1.Spec.Offering.Name = "offering" - dummies.CSMachine1.Spec.Template.Name = "template" + dummies.CSMachine1.Spec.Offering.Name = offeringName + dummies.CSMachine1.Spec.Template.Name = templateName sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID).Return(&cloudstack.ServiceOffering{Name: "offering-not-match"}, 1, nil) requiredRegexp := "offering name %s does not match name %s returned using UUID %s" @@ -633,10 +636,10 @@ var _ = Describe("Instance", func() { It("works with Id and name both provided, template name mismatch", func() { dummies.CSMachine1.Spec.Offering.ID = offeringFakeID dummies.CSMachine1.Spec.Template.ID = templateFakeID - dummies.CSMachine1.Spec.Offering.Name = "offering" - dummies.CSMachine1.Spec.Template.Name = "template" + dummies.CSMachine1.Spec.Offering.Name = offeringName + dummies.CSMachine1.Spec.Template.Name = templateName - sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID).Return(&cloudstack.ServiceOffering{Name: "offering"}, 1, nil) + sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID).Return(&cloudstack.ServiceOffering{Name: offeringName}, 1, nil) ts.EXPECT().GetTemplateByID(dummies.CSMachine1.Spec.Template.ID, executableFilter).Return(&cloudstack.Template{Name: "template-not-match"}, 1, nil) requiredRegexp := "template name %s does not match name %s returned using UUID %s" Ω(client.GetOrCreateVMInstance( @@ -648,12 +651,12 @@ var _ = Describe("Instance", func() { dummies.CSMachine1.Spec.Offering.ID = offeringFakeID dummies.CSMachine1.Spec.Template.ID = templateFakeID dummies.CSMachine1.Spec.DiskOffering.ID = diskOfferingFakeID - dummies.CSMachine1.Spec.Offering.Name = "offering" - dummies.CSMachine1.Spec.Template.Name = "template" + dummies.CSMachine1.Spec.Offering.Name = offeringName + dummies.CSMachine1.Spec.Template.Name = templateName dummies.CSMachine1.Spec.DiskOffering.Name = "diskoffering" - sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID).Return(&cloudstack.ServiceOffering{Name: "offering"}, 1, nil) - ts.EXPECT().GetTemplateByID(dummies.CSMachine1.Spec.Template.ID, executableFilter).Return(&cloudstack.Template{Name: "template"}, 1, nil) + sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID).Return(&cloudstack.ServiceOffering{Name: offeringName}, 1, nil) + ts.EXPECT().GetTemplateByID(dummies.CSMachine1.Spec.Template.ID, executableFilter).Return(&cloudstack.Template{Name: templateName}, 1, nil) dos.EXPECT().GetDiskOfferingID(dummies.CSMachine1.Spec.DiskOffering.Name, gomock.Any()).Return(diskOfferingFakeID+"-not-match", 1, nil) requiredRegexp := "diskOffering ID %s does not match ID %s returned using name %s" Ω(client.GetOrCreateVMInstance( @@ -666,8 +669,8 @@ var _ = Describe("Instance", func() { dummies.CSMachine1.Spec.DiskOffering.ID = diskOfferingFakeID dummies.CSMachine1.Spec.Offering.ID = "" dummies.CSMachine1.Spec.Template.ID = "" - dummies.CSMachine1.Spec.Offering.Name = "offering" - dummies.CSMachine1.Spec.Template.Name = "template" + dummies.CSMachine1.Spec.Offering.Name = offeringName + dummies.CSMachine1.Spec.Template.Name = templateName dummies.CSMachine1.Spec.UncompressedUserData = pointer.Bool(true) vms.EXPECT(). @@ -710,7 +713,7 @@ var _ = Describe("Instance", func() { func(p interface{}) { params := p.(*cloudstack.DeployVirtualMachineParams) displayName, _ := params.GetDisplayname() - Ω(displayName == dummies.CAPIMachine.Name).Should(BeTrue()) + Ω(displayName).Should(Equal(dummies.CAPIMachine.Name)) // Ensure the user data is only base64 encoded. b64UserData, _ := params.GetUserdata() @@ -751,7 +754,7 @@ var _ = Describe("Instance", func() { listVolumesParams.SetKeyword("DATA-") vms.EXPECT().NewDestroyVirtualMachineParams(*dummies.CSMachine1.Spec.InstanceID). Return(expungeDestroyParams) - vms.EXPECT().DestroyVirtualMachine(expungeDestroyParams).Return(nil, fmt.Errorf("unable to find uuid for id")) + vms.EXPECT().DestroyVirtualMachine(expungeDestroyParams).Return(nil, errors.New("unable to find uuid for id")) vs.EXPECT().NewListVolumesParams().Return(listVolumesParams) vs.EXPECT().ListVolumes(listVolumesParams).Return(listVolumesResponse, nil) Ω(client.DestroyVMInstance(dummies.CSMachine1)). @@ -764,7 +767,7 @@ var _ = Describe("Instance", func() { listVolumesParams.SetKeyword("DATA-") vms.EXPECT().NewDestroyVirtualMachineParams(*dummies.CSMachine1.Spec.InstanceID). Return(expungeDestroyParams) - vms.EXPECT().DestroyVirtualMachine(expungeDestroyParams).Return(nil, fmt.Errorf("new error")) + vms.EXPECT().DestroyVirtualMachine(expungeDestroyParams).Return(nil, errors.New("new error")) vs.EXPECT().NewListVolumesParams().Return(listVolumesParams) vs.EXPECT().ListVolumes(listVolumesParams).Return(listVolumesResponse, nil) Ω(client.DestroyVMInstance(dummies.CSMachine1)).Should(MatchError("new error")) @@ -849,10 +852,10 @@ var _ = Describe("Instance", func() { Ω(ok).To(BeTrue()) ids, ok := p.GetVolumeids() - Ω(len(ids)).To(Equal(0)) + Ω(ids).To(BeEmpty()) Ω(ok).To(BeFalse()) - return nil, nil + return &cloudstack.DestroyVirtualMachineResponse{}, nil }) vs.EXPECT().NewListVolumesParams().Times(0) vs.EXPECT().ListVolumes(gomock.Any()).Times(0) diff --git a/pkg/cloud/isolated_network.go b/pkg/cloud/isolated_network.go index 27a2ad23..66b43117 100644 --- a/pkg/cloud/isolated_network.go +++ b/pkg/cloud/isolated_network.go @@ -27,33 +27,39 @@ import ( "github.com/hashicorp/go-multierror" "github.com/pkg/errors" utilsnet "k8s.io/utils/net" + "sigs.k8s.io/cluster-api/util/record" + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" capcstrings "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/utils/strings" - "sigs.k8s.io/cluster-api/util/record" ) type IsoNetworkIface interface { - GetOrCreateIsolatedNetwork(*infrav1.CloudStackFailureDomain, *infrav1.CloudStackIsolatedNetwork, *infrav1.CloudStackCluster) error - ReconcileLoadBalancer(*infrav1.CloudStackFailureDomain, *infrav1.CloudStackIsolatedNetwork, *infrav1.CloudStackCluster) error + GetOrCreateIsolatedNetwork(fd *infrav1.CloudStackFailureDomain, isoNet *infrav1.CloudStackIsolatedNetwork, csCluster *infrav1.CloudStackCluster) error + ReconcileLoadBalancer(fd *infrav1.CloudStackFailureDomain, isoNet *infrav1.CloudStackIsolatedNetwork, csCluster *infrav1.CloudStackCluster) error - AssociatePublicIPAddress(*infrav1.CloudStackFailureDomain, *infrav1.CloudStackIsolatedNetwork, string) (*cloudstack.PublicIpAddress, error) - CreateEgressFirewallRules(*infrav1.CloudStackIsolatedNetwork) error - GetPublicIP(*infrav1.CloudStackFailureDomain, string) (*cloudstack.PublicIpAddress, error) + AssociatePublicIPAddress(fd *infrav1.CloudStackFailureDomain, isoNet *infrav1.CloudStackIsolatedNetwork, desiredIP string) (*cloudstack.PublicIpAddress, error) + CreateEgressFirewallRules(isoNet *infrav1.CloudStackIsolatedNetwork) error + GetPublicIP(fd *infrav1.CloudStackFailureDomain, desiredIP string) (*cloudstack.PublicIpAddress, error) GetLoadBalancerRules(isoNet *infrav1.CloudStackIsolatedNetwork) ([]*cloudstack.LoadBalancerRule, error) ReconcileLoadBalancerRules(isoNet *infrav1.CloudStackIsolatedNetwork, csCluster *infrav1.CloudStackCluster) error GetFirewallRules(isoNet *infrav1.CloudStackIsolatedNetwork) ([]*cloudstack.FirewallRule, error) ReconcileFirewallRules(isoNet *infrav1.CloudStackIsolatedNetwork, csCluster *infrav1.CloudStackCluster) error AssignVMToLoadBalancerRules(isoNet *infrav1.CloudStackIsolatedNetwork, instanceID string) error - DeleteNetwork(infrav1.Network) error - DisposeIsoNetResources(*infrav1.CloudStackIsolatedNetwork, *infrav1.CloudStackCluster) error + DeleteNetwork(net infrav1.Network) error + DisposeIsoNetResources(isoNet *infrav1.CloudStackIsolatedNetwork, csCluster *infrav1.CloudStackCluster) error } +const ( + K8sDefaultAPIPort = 6443 +) + // getNetworkOfferingID fetches the id of a network offering. func (c *client) getNetworkOfferingID() (string, error) { offeringID, count, retErr := c.cs.NetworkOffering.GetNetworkOfferingID(NetOffering) if retErr != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(retErr) + return "", retErr } else if count != 1 { return "", errors.New("found more than one network offering") @@ -85,6 +91,7 @@ func (c *client) AssociatePublicIPAddress( p.SetNetworkid(isoNet.Spec.ID) if _, err := c.cs.Address.AssociateIpAddress(p); err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return nil, errors.Wrapf(err, "associating public IP address with ID %s to network with ID %s", publicAddress.Id, isoNet.Spec.ID) @@ -97,7 +104,7 @@ func (c *client) AssociatePublicIPAddress( } // CreateIsolatedNetwork creates an isolated network in the relevant FailureDomain per passed network specification. -func (c *client) CreateIsolatedNetwork(fd *infrav1.CloudStackFailureDomain, isoNet *infrav1.CloudStackIsolatedNetwork) (retErr error) { +func (c *client) CreateIsolatedNetwork(fd *infrav1.CloudStackFailureDomain, isoNet *infrav1.CloudStackIsolatedNetwork) error { offeringID, err := c.getNetworkOfferingID() if err != nil { return err @@ -123,6 +130,7 @@ func (c *client) CreateIsolatedNetwork(fd *infrav1.CloudStackFailureDomain, isoN resp, err := c.cs.Network.CreateNetwork(p) if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return errors.Wrapf(err, "creating network with name %s", isoNet.Spec.Name) } isoNet.Spec.ID = resp.Id @@ -153,6 +161,7 @@ func (c *client) CreateEgressFirewallRules(isoNet *infrav1.CloudStackIsolatedNet } } c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(retErr) + return retErr } @@ -168,6 +177,7 @@ func (c *client) GetPublicIP( publicAddresses, err := c.cs.Address.ListPublicIpAddresses(p) if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return nil, err } else if desiredIP != "" && publicAddresses.Count == 1 { // Desired IP specified and IP found. @@ -180,8 +190,10 @@ func (c *client) GetPublicIP( return v, nil } } + return nil, errors.New("all Public IP Address(es) found were already allocated") } + return nil, errors.New("no public addresses found in available networks") } @@ -197,18 +209,21 @@ func (c *client) GetIsolatedNetwork(isoNet *infrav1.CloudStackIsolatedNetwork) ( } else { // Got netID from the network's name. isoNet.Spec.ID = netDetails.Id isoNet.Spec.CIDR = netDetails.Cidr + return nil } netDetails, count, err = c.cs.Network.GetNetworkByID(isoNet.Spec.ID) if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return multierror.Append(retErr, errors.Wrapf(err, "could not get Network by ID %s", isoNet.Spec.ID)) } else if count != 1 { return multierror.Append(retErr, errors.Errorf("expected 1 Network with UUID %s, but got %d", isoNet.Spec.ID, count)) } isoNet.Spec.Name = netDetails.Name isoNet.Spec.CIDR = netDetails.Cidr + return nil } @@ -219,6 +234,7 @@ func (c *client) GetLoadBalancerRules(isoNet *infrav1.CloudStackIsolatedNetwork) loadBalancerRules, err := c.cs.LoadBalancer.ListLoadBalancerRules(p) if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return nil, errors.Wrap(err, "listing load balancer rules") } @@ -235,6 +251,7 @@ func (c *client) ReconcileLoadBalancerRules(isoNet *infrav1.CloudStackIsolatedNe lbr, err := c.GetLoadBalancerRules(isoNet) if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return errors.Wrap(err, "retrieving load balancer rules") } @@ -306,6 +323,7 @@ func (c *client) ensureLoadBalancerRules(isoNet *infrav1.CloudStackIsolatedNetwo isoNet.Status.LBRuleID = ruleID } } + return lbRuleIDs, nil } @@ -321,6 +339,7 @@ func (c *client) getOrCreateLoadBalancerRule(isoNet *infrav1.CloudStackIsolatedN if err != nil { return "", errors.Wrap(err, "creating load balancer rule") } + return ruleID, nil } @@ -337,6 +356,7 @@ func (c *client) cleanupObsoleteLoadBalancerRules(portsAndIDs map[string]string, } } } + return nil } @@ -360,6 +380,7 @@ func (c *client) deleteLoadBalancerRuleByID(ruleID string) error { if !success { return errors.New("delete load balancer rule returned unsuccessful") } + return nil } @@ -422,6 +443,7 @@ func (c *client) GetFirewallRules(isoNet *infrav1.CloudStackIsolatedNetwork) ([] fwRules, err := c.cs.Firewall.ListFirewallRules(p) if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return nil, errors.Wrap(err, "listing firewall rules") } @@ -438,6 +460,7 @@ func (c *client) ReconcileFirewallRules(isoNet *infrav1.CloudStackIsolatedNetwor fwr, err := c.GetFirewallRules(isoNet) if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return errors.Wrap(err, "retrieving firewall rules") } @@ -711,8 +734,8 @@ func (c *client) ReconcileLoadBalancer( } else if isoNet.Spec.ControlPlaneEndpoint.Port != 0 { // Override default public port if endpoint port specified. csCluster.Spec.ControlPlaneEndpoint.Port = isoNet.Spec.ControlPlaneEndpoint.Port } else { - csCluster.Spec.ControlPlaneEndpoint.Port = 6443 - isoNet.Spec.ControlPlaneEndpoint.Port = 6443 + csCluster.Spec.ControlPlaneEndpoint.Port = K8sDefaultAPIPort + isoNet.Spec.ControlPlaneEndpoint.Port = K8sDefaultAPIPort } // Associate public IP with the load balancer if enabled. @@ -793,6 +816,7 @@ func (c *client) AssignVMToLoadBalancerRules(isoNet *infrav1.CloudStackIsolatedN func (c *client) DeleteNetwork(net infrav1.Network) error { _, err := c.cs.Network.DeleteNetwork(c.cs.Network.NewDeleteNetworkParams(net.ID)) c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return errors.Wrapf(err, "deleting network with id %s", net.ID) } @@ -831,7 +855,7 @@ func (c *client) DisposeIsoNetResources( } // DeleteNetworkIfNotInUse deletes an isolated network if the network is no longer in use (indicated by in use tags). -func (c *client) DeleteNetworkIfNotInUse(net infrav1.Network) (retError error) { +func (c *client) DeleteNetworkIfNotInUse(net infrav1.Network) error { tags, err := c.GetTags(ResourceTypeNetwork, net.ID) if err != nil { return err @@ -862,6 +886,7 @@ func (c *client) DisassociatePublicIPAddressIfNotInUse(ipAddressID string) (bool return false, err } else if publicIP, _, err := c.cs.Address.GetPublicIpAddressByID(ipAddressID); err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return false, err } else if publicIP == nil || publicIP.Issourcenat { // Can't disassociate an address if it's the source NAT address. return false, nil @@ -901,7 +926,7 @@ func parseCIDR(cidr string) (map[string]string, error) { ip, ipnet, err := net.ParseCIDR(cidr) if err != nil { - return nil, fmt.Errorf("unable to parse cidr %s: %s", cidr, err) + return nil, fmt.Errorf("unable to parse cidr %s: %w", cidr, err) } msk := ipnet.Mask diff --git a/pkg/cloud/isolated_network_test.go b/pkg/cloud/isolated_network_test.go index 7846c45c..cae0ff86 100644 --- a/pkg/cloud/isolated_network_test.go +++ b/pkg/cloud/isolated_network_test.go @@ -17,7 +17,6 @@ limitations under the License. package cloud_test import ( - "k8s.io/utils/pointer" "strconv" csapi "github.com/apache/cloudstack-go/v2/cloudstack" @@ -25,12 +24,13 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/pkg/errors" + "k8s.io/utils/pointer" + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" ) var _ = Describe("Network", func() { - const ( ipAddress = "192.168.1.14" errorMessage = "Error" @@ -82,10 +82,11 @@ var _ = Describe("Network", func() { fs.EXPECT().NewCreateEgressFirewallRuleParams(dummies.ISONet1.ID, gomock.Any()). DoAndReturn(func(networkid string, protocol string) *csapi.CreateEgressFirewallRuleParams { p := &csapi.CreateEgressFirewallRuleParams{} - if protocol == "icmp" { + if protocol == cloud.NetworkProtocolICMP { p.SetIcmptype(-1) p.SetIcmpcode(-1) } + return p }).Times(3) @@ -115,10 +116,11 @@ var _ = Describe("Network", func() { fs.EXPECT().NewCreateEgressFirewallRuleParams(dummies.ISONet1.ID, gomock.Any()). DoAndReturn(func(networkid string, protocol string) *csapi.CreateEgressFirewallRuleParams { p := &csapi.CreateEgressFirewallRuleParams{} - if protocol == "icmp" { + if protocol == cloud.NetworkProtocolICMP { p.SetIcmptype(-1) p.SetIcmpcode(-1) } + return p }).Times(3) @@ -152,10 +154,11 @@ var _ = Describe("Network", func() { fs.EXPECT().NewCreateEgressFirewallRuleParams(dummies.ISONet1.ID, gomock.Any()). DoAndReturn(func(networkid string, protocol string) *csapi.CreateEgressFirewallRuleParams { p := &csapi.CreateEgressFirewallRuleParams{} - if protocol == "icmp" { + if protocol == cloud.NetworkProtocolICMP { p.SetIcmptype(-1) p.SetIcmpcode(-1) } + return p }).Times(3) @@ -179,10 +182,11 @@ var _ = Describe("Network", func() { fs.EXPECT().NewCreateEgressFirewallRuleParams(dummies.ISONet1.ID, gomock.Any()). DoAndReturn(func(networkid string, protocol string) *csapi.CreateEgressFirewallRuleParams { p := &csapi.CreateEgressFirewallRuleParams{} - if protocol == "icmp" { + if protocol == cloud.NetworkProtocolICMP { p.SetIcmptype(-1) p.SetIcmpcode(-1) } + return p }).Times(3) @@ -232,16 +236,18 @@ var _ = Describe("Network", func() { as.EXPECT().ListPublicIpAddresses(gomock.Any()). Return(&csapi.ListPublicIpAddressesResponse{ Count: 2, - PublicIpAddresses: []*csapi.PublicIpAddress{{ - State: "Allocated", - Allocated: "true", - Associatednetworkid: "1", - }, + PublicIpAddresses: []*csapi.PublicIpAddress{ { State: "Allocated", Allocated: "true", Associatednetworkid: "1", - }}, + }, + { + State: "Allocated", + Allocated: "true", + Associatednetworkid: "1", + }, + }, }, nil) publicIPAddress, err := client.GetPublicIP(dummies.CSFailureDomain1, dummies.CSCluster.Spec.ControlPlaneEndpoint.Host) Ω(publicIPAddress).Should(BeNil()) @@ -367,6 +373,7 @@ var _ = Describe("Network", func() { fs.EXPECT().NewDeleteFirewallRuleParams(dummies.FWRuleID).DoAndReturn(func(ruleid string) *csapi.DeleteFirewallRuleParams { p := &csapi.DeleteFirewallRuleParams{} p.SetId(ruleid) + return p }) fs.EXPECT().DeleteFirewallRule(gomock.Any()).Return(&csapi.DeleteFirewallRuleResponse{Success: true}, nil).Times(1) @@ -528,7 +535,8 @@ var _ = Describe("Network", func() { lbs.EXPECT().NewListLoadBalancerRulesParams().Return(&csapi.ListLoadBalancerRulesParams{}) lbs.EXPECT().ListLoadBalancerRules(gomock.Any()). Return(&csapi.ListLoadBalancerRulesResponse{ - LoadBalancerRules: []*csapi.LoadBalancerRule{}}, nil) + LoadBalancerRules: []*csapi.LoadBalancerRule{}, + }, nil) lbs.EXPECT().NewCreateLoadBalancerRuleParams(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Return(&csapi.CreateLoadBalancerRuleParams{}) lbs.EXPECT().CreateLoadBalancerRule(gomock.Any()). @@ -628,7 +636,8 @@ var _ = Describe("Network", func() { lbs.EXPECT().NewListLoadBalancerRulesParams().Return(&csapi.ListLoadBalancerRulesParams{}) lbs.EXPECT().ListLoadBalancerRules(gomock.Any()). Return(&csapi.ListLoadBalancerRulesResponse{ - LoadBalancerRules: []*csapi.LoadBalancerRule{{Publicport: "7443", Id: dummies.LBRuleID}}}, nil) + LoadBalancerRules: []*csapi.LoadBalancerRule{{Publicport: "7443", Id: dummies.LBRuleID}}, + }, nil) lbs.EXPECT().NewCreateLoadBalancerRuleParams(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Return(&csapi.CreateLoadBalancerRuleParams{}) lbs.EXPECT().CreateLoadBalancerRule(gomock.Any()). @@ -685,6 +694,7 @@ var _ = Describe("Network", func() { fs.EXPECT().NewDeleteFirewallRuleParams("FakeFWRuleID2").DoAndReturn(func(ruleid string) *csapi.DeleteFirewallRuleParams { p := &csapi.DeleteFirewallRuleParams{} p.SetId(ruleid) + return p }) fs.EXPECT().DeleteFirewallRule(gomock.Any()).Return(&csapi.DeleteFirewallRuleResponse{Success: true}, nil).Times(1) @@ -728,6 +738,7 @@ var _ = Describe("Network", func() { fs.EXPECT().NewDeleteFirewallRuleParams("FakeFWRuleID2").DoAndReturn(func(ruleid string) *csapi.DeleteFirewallRuleParams { p := &csapi.DeleteFirewallRuleParams{} p.SetId(ruleid) + return p }) fs.EXPECT().DeleteFirewallRule(gomock.Any()).Return(&csapi.DeleteFirewallRuleResponse{Success: true}, nil).Times(1) @@ -760,6 +771,7 @@ var _ = Describe("Network", func() { p.SetStartport(int(dummies.EndPointPort)) p.SetEndport(int(dummies.EndPointPort)) p.SetProtocol(proto) + return p }).Times(1) fs.EXPECT().CreateFirewallRule(gomock.Any()).Return(&csapi.CreateFirewallRuleResponse{}, nil).Times(1) @@ -789,6 +801,7 @@ var _ = Describe("Network", func() { fs.EXPECT().NewDeleteFirewallRuleParams("FakeFWRuleID2").DoAndReturn(func(ruleid string) *csapi.DeleteFirewallRuleParams { p := &csapi.DeleteFirewallRuleParams{} p.SetId(ruleid) + return p }).Times(1) fs.EXPECT().DeleteFirewallRule(gomock.Any()).Return(&csapi.DeleteFirewallRuleResponse{Success: true}, nil).Times(1) @@ -798,6 +811,7 @@ var _ = Describe("Network", func() { p.SetStartport(int(dummies.EndPointPort)) p.SetEndport(int(dummies.EndPointPort)) p.SetProtocol(proto) + return p }).Times(1) fs.EXPECT().CreateFirewallRule(gomock.Any()).Return(&csapi.CreateFirewallRuleResponse{}, nil).Times(1) @@ -831,6 +845,7 @@ var _ = Describe("Network", func() { p.SetStartport(int(dummies.EndPointPort)) p.SetEndport(int(dummies.EndPointPort)) p.SetProtocol(proto) + return p }).Times(1), fs.EXPECT().CreateFirewallRule(gomock.Any()).Return(&csapi.CreateFirewallRuleResponse{}, nil).Times(1), @@ -840,6 +855,7 @@ var _ = Describe("Network", func() { p.SetStartport(456) p.SetEndport(456) p.SetProtocol(proto) + return p }).Times(1), fs.EXPECT().CreateFirewallRule(gomock.Any()).Return(&csapi.CreateFirewallRuleResponse{}, nil).Times(1), @@ -872,6 +888,7 @@ var _ = Describe("Network", func() { p.SetStartport(int(dummies.EndPointPort)) p.SetEndport(int(dummies.EndPointPort)) p.SetProtocol(proto) + return p }).Times(1), fs.EXPECT().CreateFirewallRule(gomock.Any()).Return(&csapi.CreateFirewallRuleResponse{}, nil).Times(1), @@ -882,6 +899,7 @@ var _ = Describe("Network", func() { p.SetStartport(int(dummies.EndPointPort)) p.SetEndport(int(dummies.EndPointPort)) p.SetProtocol(proto) + return p }).Times(1), fs.EXPECT().CreateFirewallRule(gomock.Any()).Return(&csapi.CreateFirewallRuleResponse{}, nil).Times(1), @@ -892,6 +910,7 @@ var _ = Describe("Network", func() { p.SetStartport(int(dummies.EndPointPort)) p.SetEndport(int(dummies.EndPointPort)) p.SetProtocol(proto) + return p }).Times(1), fs.EXPECT().CreateFirewallRule(gomock.Any()).Return(&csapi.CreateFirewallRuleResponse{}, nil).Times(1), @@ -952,12 +971,14 @@ var _ = Describe("Network", func() { fs.EXPECT().NewDeleteFirewallRuleParams("FakeFWRuleID1").DoAndReturn(func(ruleid string) *csapi.DeleteFirewallRuleParams { p := &csapi.DeleteFirewallRuleParams{} p.SetId(ruleid) + return p }), fs.EXPECT().DeleteFirewallRule(gomock.Any()).Return(&csapi.DeleteFirewallRuleResponse{Success: true}, nil).Times(1), fs.EXPECT().NewDeleteFirewallRuleParams("FakeFWRuleID2").DoAndReturn(func(ruleid string) *csapi.DeleteFirewallRuleParams { p := &csapi.DeleteFirewallRuleParams{} p.SetId(ruleid) + return p }), fs.EXPECT().DeleteFirewallRule(gomock.Any()).Return(&csapi.DeleteFirewallRuleResponse{Success: true}, nil).Times(1), @@ -1002,7 +1023,7 @@ var _ = Describe("Network", func() { Context("Dispose or cleanup isolate network resources", func() { It("delete all isolated network resources when not managed by CAPC", func() { - dummies.CSISONet1.Status.PublicIPID = "publicIpId" + dummies.CSISONet1.Status.PublicIPID = dummies.PublicIPID rtlp := &csapi.ListTagsParams{} rs.EXPECT().NewListTagsParams().Return(rtlp).Times(4) rs.EXPECT().ListTags(rtlp).Return(&csapi.ListTagsResponse{}, nil).Times(4) @@ -1012,7 +1033,7 @@ var _ = Describe("Network", func() { }) It("delete all isolated network resources when managed by CAPC", func() { - dummies.CSISONet1.Status.PublicIPID = "publicIpId" + dummies.CSISONet1.Status.PublicIPID = dummies.PublicIPID rtdp := &csapi.DeleteTagsParams{} rtlp := &csapi.ListTagsParams{} dap := &csapi.DisassociateIpAddressParams{} @@ -1030,7 +1051,7 @@ var _ = Describe("Network", func() { }) It("disassociate IP address fails due to failure in deleting a resource i.e., disassociate Public IP", func() { - dummies.CSISONet1.Status.PublicIPID = "publicIpId" + dummies.CSISONet1.Status.PublicIPID = dummies.PublicIPID rtdp := &csapi.DeleteTagsParams{} rtlp := &csapi.ListTagsParams{} dap := &csapi.DisassociateIpAddressParams{} @@ -1045,7 +1066,6 @@ var _ = Describe("Network", func() { Ω(client.DisposeIsoNetResources(dummies.CSISONet1, dummies.CSCluster)).ShouldNot(Succeed()) }) - }) Context("Networking Integ Tests", Label("integ"), func() { diff --git a/pkg/cloud/network.go b/pkg/cloud/network.go index 9e62f858..dc09258c 100644 --- a/pkg/cloud/network.go +++ b/pkg/cloud/network.go @@ -19,17 +19,17 @@ package cloud import ( "github.com/hashicorp/go-multierror" "github.com/pkg/errors" + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) type NetworkIface interface { - ResolveNetwork(*infrav1.Network) error - RemoveClusterTagFromNetwork(*infrav1.CloudStackCluster, infrav1.Network) error + ResolveNetwork(net *infrav1.Network) error + RemoveClusterTagFromNetwork(csCluster *infrav1.CloudStackCluster, net infrav1.Network) error } const ( NetOffering = "DefaultIsolatedNetworkOfferingWithSourceNatService" - K8sDefaultAPIPort = 6443 NetworkTypeIsolated = "Isolated" NetworkTypeShared = "Shared" NetworkProtocolTCP = "tcp" @@ -54,6 +54,7 @@ func (c *client) ResolveNetwork(net *infrav1.Network) (retErr error) { net.Type = netDetails.Type net.CIDR = netDetails.Cidr net.Domain = netDetails.Networkdomain + return nil } @@ -63,6 +64,7 @@ func (c *client) ResolveNetwork(net *infrav1.Network) (retErr error) { return multierror.Append(retErr, errors.Wrapf(err, "could not get Network by ID %s", net.ID)) } else if count != 1 { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return multierror.Append(retErr, errors.Errorf("expected 1 Network with UUID %s, but got %d", net.ID, count)) } net.Name = netDetails.Name @@ -70,6 +72,7 @@ func (c *client) ResolveNetwork(net *infrav1.Network) (retErr error) { net.Type = netDetails.Type net.CIDR = netDetails.Cidr net.Domain = netDetails.Networkdomain + return nil } @@ -78,7 +81,7 @@ func generateNetworkTagName(csCluster *infrav1.CloudStackCluster) string { } // RemoveClusterTagFromNetwork the cluster in use tag from a network. -func (c *client) RemoveClusterTagFromNetwork(csCluster *infrav1.CloudStackCluster, net infrav1.Network) (retError error) { +func (c *client) RemoveClusterTagFromNetwork(csCluster *infrav1.CloudStackCluster, net infrav1.Network) error { tags, err := c.GetTags(ResourceTypeNetwork, net.ID) if err != nil { return err diff --git a/pkg/cloud/network_test.go b/pkg/cloud/network_test.go index 10311b79..79f04610 100644 --- a/pkg/cloud/network_test.go +++ b/pkg/cloud/network_test.go @@ -24,6 +24,7 @@ import ( "github.com/golang/mock/gomock" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" ) diff --git a/pkg/cloud/tags.go b/pkg/cloud/tags.go index b02b6785..c569a882 100644 --- a/pkg/cloud/tags.go +++ b/pkg/cloud/tags.go @@ -20,20 +20,20 @@ import ( "strings" "github.com/hashicorp/go-multierror" - "github.com/pkg/errors" + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) type TagIface interface { - AddClusterTag(ResourceType, string, *infrav1.CloudStackCluster) error - DeleteClusterTag(ResourceType, string, *infrav1.CloudStackCluster) error - AddCreatedByCAPCTag(ResourceType, string) error - DeleteCreatedByCAPCTag(ResourceType, string) error - DoClusterTagsAllowDisposal(ResourceType, string) (bool, error) - AddTags(ResourceType, string, map[string]string) error - GetTags(ResourceType, string) (map[string]string, error) - DeleteTags(ResourceType, string, map[string]string) error + AddClusterTag(rType ResourceType, rID string, csCluster *infrav1.CloudStackCluster) error + DeleteClusterTag(rType ResourceType, rID string, csCluster *infrav1.CloudStackCluster) error + AddCreatedByCAPCTag(rType ResourceType, rID string) error + DeleteCreatedByCAPCTag(rType ResourceType, rID string) error + DoClusterTagsAllowDisposal(rType ResourceType, rID string) (bool, error) + AddTags(rType ResourceType, rID string, tags map[string]string) error + GetTags(rType ResourceType, rID string) (map[string]string, error) + DeleteTags(rType ResourceType, rID string, tagsToDelete map[string]string) error } type ResourceType string @@ -53,6 +53,7 @@ func ignoreAlreadyPresentErrors(err error, rType ResourceType, rID string) error if err != nil && !strings.Contains(strings.ToLower(err.Error()), matchSubString) { return err } + return nil } @@ -64,6 +65,7 @@ func (c *client) IsCapcManaged(resourceType ResourceType, resourceID string) (bo "checking if %s with ID: %s is tagged as CAPC managed", resourceType, resourceID) } _, CreatedByCAPC := tags[CreatedByCAPCTagName] + return CreatedByCAPC, nil } @@ -73,8 +75,10 @@ func (c *client) AddClusterTag(rType ResourceType, rID string, csCluster *infrav return err } else if managedByCAPC { ClusterTagName := generateClusterTagName(csCluster) + return c.AddTags(rType, rID, map[string]string{ClusterTagName: "1"}) } + return nil } @@ -84,8 +88,10 @@ func (c *client) DeleteClusterTag(rType ResourceType, rID string, csCluster *inf return err } else if managedByCAPC { ClusterTagName := generateClusterTagName(csCluster) + return c.DeleteTags(rType, rID, map[string]string{ClusterTagName: "1"}) } + return nil } @@ -102,8 +108,8 @@ func (c *client) DeleteCreatedByCAPCTag(rType ResourceType, rID string) error { // DoClusterTagsAllowDisposal checks to see if the resource is in a state that makes it eligible for disposal. CAPC can // dispose of a resource if the tags show it was created by CAPC and isn't being used by any clusters. -func (c *client) DoClusterTagsAllowDisposal(resourceType ResourceType, resourceID string) (bool, error) { - tags, err := c.GetTags(resourceType, resourceID) +func (c *client) DoClusterTagsAllowDisposal(rType ResourceType, rID string) (bool, error) { + tags, err := c.GetTags(rType, rID) if err != nil { return false, err } @@ -119,49 +125,53 @@ func (c *client) DoClusterTagsAllowDisposal(resourceType ResourceType, resourceI } // AddTags adds arbitrary tags to a resource. -func (c *client) AddTags(resourceType ResourceType, resourceID string, tags map[string]string) error { - p := c.cs.Resourcetags.NewCreateTagsParams([]string{resourceID}, string(resourceType), tags) +func (c *client) AddTags(rType ResourceType, rID string, tags map[string]string) error { + p := c.cs.Resourcetags.NewCreateTagsParams([]string{rID}, string(rType), tags) _, err := c.cs.Resourcetags.CreateTags(p) c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) - return ignoreAlreadyPresentErrors(err, resourceType, resourceID) + + return ignoreAlreadyPresentErrors(err, rType, rID) } // GetTags gets all of a resource's tags. -func (c *client) GetTags(resourceType ResourceType, resourceID string) (map[string]string, error) { +func (c *client) GetTags(rType ResourceType, rID string) (map[string]string, error) { p := c.cs.Resourcetags.NewListTagsParams() - p.SetResourceid(resourceID) - p.SetResourcetype(string(resourceType)) + p.SetResourceid(rID) + p.SetResourcetype(string(rType)) p.SetListall(true) listTagResponse, err := c.cs.Resourcetags.ListTags(p) if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return nil, err } tags := make(map[string]string, listTagResponse.Count) for _, t := range listTagResponse.Tags { tags[t.Key] = t.Value } + return tags, nil } // DeleteTags deletes the given tags from a resource. // Ignores errors if the tag is not present. -func (c *client) DeleteTags(resourceType ResourceType, resourceID string, tagsToDelete map[string]string) error { +func (c *client) DeleteTags(rType ResourceType, rID string, tagsToDelete map[string]string) error { for tagkey, tagval := range tagsToDelete { - p := c.cs.Resourcetags.NewDeleteTagsParams([]string{resourceID}, string(resourceType)) + p := c.cs.Resourcetags.NewDeleteTagsParams([]string{rID}, string(rType)) p.SetTags(tagsToDelete) if _, err1 := c.cs.Resourcetags.DeleteTags(p); err1 != nil { // Error in deletion attempt. Check for tag. c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err1) currTag := map[string]string{tagkey: tagval} - if tags, err2 := c.GetTags(resourceType, resourceID); len(tags) != 0 { + if tags, err2 := c.GetTags(rType, rID); len(tags) != 0 { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err2) if _, foundTag := tags[tagkey]; foundTag { return errors.Wrapf(multierror.Append(err1, err2), - "could not remove tag %s from %s with ID %s", currTag, resourceType, resourceID) + "could not remove tag %s from %s with ID %s", currTag, rType, rID) } } } } + return nil } diff --git a/pkg/cloud/tags_test.go b/pkg/cloud/tags_test.go index ccb07d8f..27beea82 100644 --- a/pkg/cloud/tags_test.go +++ b/pkg/cloud/tags_test.go @@ -22,6 +22,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/pkg/errors" + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" ) @@ -102,7 +103,7 @@ var _ = Describe("Tag Unit Tests", func() { // Verify tags tags, err := client.GetTags(cloud.ResourceTypeNetwork, dummies.CSISONet1.Spec.ID) - Ω(err).Should(BeNil()) + Ω(err).ShouldNot(HaveOccurred()) Ω(tags[cloud.CreatedByCAPCTagName]).Should(Equal("")) Ω(tags[dummies.CSClusterTagKey]).Should(Equal("")) }) @@ -129,14 +130,14 @@ var _ = Describe("Tag Unit Tests", func() { It("does not allow a resource to be deleted when there are no tags", func() { tagsAllowDisposal, err := client.DoClusterTagsAllowDisposal(cloud.ResourceTypeNetwork, dummies.CSISONet1.Spec.ID) - Ω(err).Should(BeNil()) + Ω(err).ShouldNot(HaveOccurred()) Ω(tagsAllowDisposal).Should(BeFalse()) }) It("does not allow a resource to be deleted when there is a cluster tag", func() { Ω(client.AddClusterTag(cloud.ResourceTypeNetwork, dummies.CSISONet1.Spec.ID, dummies.CSCluster)).Should(Succeed()) tagsAllowDisposal, err := client.DoClusterTagsAllowDisposal(cloud.ResourceTypeNetwork, dummies.CSISONet1.Spec.ID) - Ω(err).Should(BeNil()) + Ω(err).ShouldNot(HaveOccurred()) Ω(tagsAllowDisposal).Should(BeFalse()) }) @@ -146,7 +147,7 @@ var _ = Describe("Tag Unit Tests", func() { Ω(client.DeleteClusterTag(cloud.ResourceTypeNetwork, dummies.CSISONet1.Spec.ID, dummies.CSCluster)).Should(Succeed()) tagsAllowDisposal, err := client.DoClusterTagsAllowDisposal(cloud.ResourceTypeNetwork, dummies.CSISONet1.Spec.ID) - Ω(err).Should(BeNil()) + Ω(err).ShouldNot(HaveOccurred()) Ω(tagsAllowDisposal).Should(BeTrue()) }) }) diff --git a/pkg/cloud/user_credentials.go b/pkg/cloud/user_credentials.go index 15d2d22f..9acd2818 100644 --- a/pkg/cloud/user_credentials.go +++ b/pkg/cloud/user_credentials.go @@ -28,11 +28,11 @@ const ( ) type UserCredIFace interface { - ResolveDomain(*Domain) error - ResolveAccount(*Account) error - ResolveUser(*User) error - ResolveUserKeys(*User) error - GetUserWithKeys(*User) (bool, error) + ResolveDomain(domain *Domain) error + ResolveAccount(account *Account) error + ResolveUser(user *User) error + ResolveUserKeys(user *User) error + GetUserWithKeys(user *User) (bool, error) } // Domain contains specifications that identify a domain. @@ -98,6 +98,7 @@ func (c *client) ResolveDomain(domain *Domain) error { resp, retErr := c.cs.Domain.ListDomains(p) if retErr != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(retErr) + return retErr } @@ -114,6 +115,7 @@ func (c *client) ResolveDomain(domain *Domain) error { domain.CPUAvailable = resp.Domains[0].Cpuavailable domain.MemoryAvailable = resp.Domains[0].Memoryavailable domain.VMAvailable = resp.Domains[0].Vmavailable + return nil } @@ -127,13 +129,15 @@ func (c *client) ResolveDomain(domain *Domain) error { // Finally, search for the domain by Path. for _, possibleDomain := range resp.Domains { - if possibleDomain.Path == domain.Path { - domain.ID = possibleDomain.Id - domain.CPUAvailable = possibleDomain.Cpuavailable - domain.MemoryAvailable = possibleDomain.Memoryavailable - domain.VMAvailable = possibleDomain.Vmavailable - return nil + if possibleDomain.Path != domain.Path { + continue } + domain.ID = possibleDomain.Id + domain.CPUAvailable = possibleDomain.Cpuavailable + domain.MemoryAvailable = possibleDomain.Memoryavailable + domain.VMAvailable = possibleDomain.Vmavailable + + return nil } return errors.Errorf("domain not found for domain path %s", domain.Path) @@ -153,6 +157,7 @@ func (c *client) ResolveAccount(account *Account) error { resp, retErr := c.cs.Account.ListAccounts(p) if retErr != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(retErr) + return retErr } else if resp.Count == 0 { return errors.Errorf("could not find account %s", account.Name) @@ -165,6 +170,7 @@ func (c *client) ResolveAccount(account *Account) error { account.CPUAvailable = resp.Accounts[0].Cpuavailable account.MemoryAvailable = resp.Accounts[0].Memoryavailable account.VMAvailable = resp.Accounts[0].Vmavailable + return nil } @@ -183,6 +189,7 @@ func (c *client) ResolveUser(user *User) error { resp, err := c.cs.User.ListUsers(p) if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return err } else if resp.Count != 1 { return errors.Errorf("expected 1 User with username %s but got %d", user.Name, resp.Count) @@ -205,10 +212,12 @@ func (c *client) ResolveUserKeys(user *User) error { resp, err := c.cs.User.GetUserKeys(p) if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return errors.Errorf("error encountered when resolving user api keys for user %s", user.Name) } user.APIKey = resp.Apikey user.SecretKey = resp.Secretkey + return nil } @@ -228,6 +237,7 @@ func (c *client) GetUserWithKeys(user *User) (bool, error) { resp, err := c.cs.User.ListUsers(p) if err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return false, err } @@ -239,5 +249,6 @@ func (c *client) GetUserWithKeys(user *User) (bool, error) { } } user.ID = "" + return false, nil } diff --git a/pkg/cloud/user_credentials_test.go b/pkg/cloud/user_credentials_test.go index f7953fbb..badcda3e 100644 --- a/pkg/cloud/user_credentials_test.go +++ b/pkg/cloud/user_credentials_test.go @@ -23,11 +23,11 @@ import ( csapi "github.com/apache/cloudstack-go/v2/cloudstack" "github.com/golang/mock/gomock" . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" "sigs.k8s.io/cluster-api-provider-cloudstack/test/helpers" - - . "github.com/onsi/gomega" ) var _ = Describe("User Credentials", func() { @@ -143,7 +143,6 @@ var _ = Describe("User Credentials", func() { }}}, nil) Ω(client.ResolveAccount(&dummies.Account)).Should(Succeed()) - }) It("no account found in CloudStack for the provided Account name", func() { @@ -334,7 +333,6 @@ var _ = Describe("User Credentials", func() { err := client.ResolveUserKeys(&dummies.User) Ω(err).ShouldNot(Succeed()) Ω(err.Error()).Should(ContainSubstring("error encountered when resolving user details")) - }) It("get user keys fils when resolving user keys", func() { diff --git a/pkg/cloud/zone.go b/pkg/cloud/zone.go index 73288bc5..5d00c031 100644 --- a/pkg/cloud/zone.go +++ b/pkg/cloud/zone.go @@ -19,12 +19,13 @@ package cloud import ( "github.com/hashicorp/go-multierror" "github.com/pkg/errors" + infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" ) type ZoneIFace interface { - ResolveZone(*infrav1.CloudStackZoneSpec) error - ResolveNetworkForZone(*infrav1.CloudStackZoneSpec) error + ResolveZone(zSpec *infrav1.CloudStackZoneSpec) error + ResolveNetworkForZone(zSpec *infrav1.CloudStackZoneSpec) error } func (c *client) ResolveZone(zSpec *infrav1.CloudStackZoneSpec) (retErr error) { @@ -41,6 +42,7 @@ func (c *client) ResolveZone(zSpec *infrav1.CloudStackZoneSpec) (retErr error) { if resp, count, err := c.cs.Zone.GetZoneByID(zSpec.ID); err != nil { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return multierror.Append(retErr, errors.Wrapf(err, "could not get Zone by ID %v", zSpec.ID)) } else if count != 1 { return multierror.Append(retErr, errors.Errorf( @@ -65,6 +67,7 @@ func (c *client) ResolveNetworkForZone(zSpec *infrav1.CloudStackZoneSpec) (retEr } else { // Got netID from the network's name. zSpec.Network.ID = netDetails.Id zSpec.Network.Type = netDetails.Type + return nil } @@ -74,10 +77,12 @@ func (c *client) ResolveNetworkForZone(zSpec *infrav1.CloudStackZoneSpec) (retEr return multierror.Append(retErr, errors.Wrapf(err, "could not get Network by ID %s", zSpec.Network.ID)) } else if count != 1 { c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err) + return multierror.Append(retErr, errors.Errorf("expected 1 Network with UUID %v, but got %d", zSpec.Network.ID, count)) } zSpec.Network.Name = netDetails.Name zSpec.Network.ID = netDetails.Id zSpec.Network.Type = netDetails.Type + return nil } diff --git a/pkg/cloud/zone_test.go b/pkg/cloud/zone_test.go index 5217f3e4..4f91fd2f 100644 --- a/pkg/cloud/zone_test.go +++ b/pkg/cloud/zone_test.go @@ -24,6 +24,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/pkg/errors" + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud" dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3" ) @@ -57,7 +58,7 @@ var _ = Describe("Zone", func() { Context("an existing abstract dummies.CSCluster", func() { It("handles zone not found.", func() { - expectedErr := fmt.Errorf("Not found") + expectedErr := errors.New("Not found") zs.EXPECT().GetZoneID(dummies.Zone1.Name).Return("", -1, expectedErr) zs.EXPECT().GetZoneByID(dummies.Zone1.ID).Return(nil, -1, expectedErr) @@ -67,7 +68,7 @@ var _ = Describe("Zone", func() { It("handles multiple zone IDs returned", func() { zs.EXPECT().GetZoneID(dummies.Zone1.Name).Return(dummies.Zone1.ID, 2, nil) - zs.EXPECT().GetZoneByID(dummies.Zone1.ID).Return(nil, -1, fmt.Errorf("Not found")) + zs.EXPECT().GetZoneByID(dummies.Zone1.ID).Return(nil, -1, errors.New("Not found")) Ω(client.ResolveZone(&dummies.CSFailureDomain1.Spec.Zone)).Should(MatchError(And( ContainSubstring("expected 1 Zone with name "+dummies.Zone1.Name+", but got 2"), @@ -109,7 +110,7 @@ var _ = Describe("Zone", func() { ns.EXPECT().GetNetworkByName(dummies.Zone2.Network.Name).Return(nil, -1, fakeError) ns.EXPECT().GetNetworkByID(dummies.Zone2.Network.ID).Return(nil, -1, fakeError) - Ω(client.ResolveNetworkForZone(&dummies.CSFailureDomain2.Spec.Zone).Error()).Should(ContainSubstring(fmt.Sprintf("could not get Network by ID %s", dummies.Zone2.Network.ID))) + Ω(client.ResolveNetworkForZone(&dummies.CSFailureDomain2.Spec.Zone).Error()).Should(ContainSubstring("could not get Network by ID " + dummies.Zone2.Network.ID)) }) }) }) diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index 77e9413f..738b993f 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -18,8 +18,10 @@ limitations under the License. package metrics import ( - "github.com/prometheus/client_golang/prometheus" "regexp" + + "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" crtlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics" ) @@ -34,13 +36,14 @@ func NewCustomMetrics() ACSCustomMetrics { customMetrics := ACSCustomMetrics{} customMetrics.acsReconciliationErrorCount = prometheus.NewCounterVec( prometheus.CounterOpts{ - Name: "acs_reconciliation_errors", + Name: "acs_reconciliation_errors_total", Help: "Count of reconciliation errors caused by ACS issues, bucketed by error code", }, []string{"acs_error_code"}, ) if err := crtlmetrics.Registry.Register(customMetrics.acsReconciliationErrorCount); err != nil { - if are, ok := err.(prometheus.AlreadyRegisteredError); ok { + are := prometheus.AlreadyRegisteredError{} + if errors.As(err, &are) { customMetrics.acsReconciliationErrorCount = are.ExistingCollector.(*prometheus.CounterVec) } else { // Something else went wrong! @@ -49,8 +52,8 @@ func NewCustomMetrics() ACSCustomMetrics { } // ACS standard error messages of the form "CloudStack API error 431 (CSExceptionErrorCode: 9999):..." - // This regexp is used to extract CSExceptionCodes from the message. - customMetrics.errorCodeRegexp, _ = regexp.Compile(".+CSExceptionErrorCode: ([0-9]+).+") + // This regexp is used to extract CSExceptionCodes from the message. + customMetrics.errorCodeRegexp = regexp.MustCompile(".+CSExceptionErrorCode: ([0-9]+).+") return customMetrics } diff --git a/pkg/utils/strings/strings.go b/pkg/utils/strings/strings.go index 91c36cdd..659451f5 100644 --- a/pkg/utils/strings/strings.go +++ b/pkg/utils/strings/strings.go @@ -23,6 +23,7 @@ import ( func Canonicalize[S ~[]E, E cmp.Ordered](s S) S { slices.Sort(s) + return slices.Compact(s) } @@ -50,5 +51,6 @@ func SliceDiff[T ~[]E, E comparable](list1 T, list2 T) (ret1 T, ret2 T) { ret2 = append(ret2, v) } } + return ret1, ret2 } diff --git a/pkg/utils/strings/strings_test.go b/pkg/utils/strings/strings_test.go index 9a6bb6df..247a3ba2 100644 --- a/pkg/utils/strings/strings_test.go +++ b/pkg/utils/strings/strings_test.go @@ -14,11 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -package strings +package strings_test import ( "slices" "testing" + + "sigs.k8s.io/cluster-api-provider-cloudstack/pkg/utils/strings" ) func TestCanonicalize(t *testing.T) { @@ -51,7 +53,7 @@ func TestCanonicalize(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := Canonicalize(tt.value) + got := strings.Canonicalize(tt.value) if !slices.Equal(got, tt.want) { t.Errorf("CompareLists() = %v, want %v", got, tt.want) } @@ -111,7 +113,7 @@ func TestSliceDiff(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - res1, res2 := SliceDiff(tt.value1, tt.value2) + res1, res2 := strings.SliceDiff(tt.value1, tt.value2) if !slices.Equal(res1, tt.want1) { t.Errorf("CompareLists() = %v, want %v", res1, tt.want1) } diff --git a/pkg/webhookutil/webhook_validators.go b/pkg/webhookutil/webhook_validators.go index bf442ad9..b6f2dc2a 100644 --- a/pkg/webhookutil/webhook_validators.go +++ b/pkg/webhookutil/webhook_validators.go @@ -28,6 +28,7 @@ func EnsureFieldExists(value string, name string, allErrs field.ErrorList) field if value == "" { allErrs = append(allErrs, field.Required(field.NewPath("spec", name), name)) } + return allErrs } @@ -35,33 +36,37 @@ func EnsureAtLeastOneFieldExists(value1 string, value2 string, name string, allE if value1 == "" && value2 == "" { allErrs = append(allErrs, field.Required(field.NewPath("spec", name), name)) } + return allErrs } -func EnsureEqualStrings(new string, old string, name string, allErrs field.ErrorList) field.ErrorList { - if new != old { +func EnsureEqualStrings(newStr string, oldStr string, name string, allErrs field.ErrorList) field.ErrorList { + if newStr != oldStr { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", name), name)) } + return allErrs } -func EnsureIntFieldsAreNotNegative(new int64, name string, allErrs field.ErrorList) field.ErrorList { - if new < 0 { +func EnsureIntFieldsAreNotNegative(newInt int64, name string, allErrs field.ErrorList) field.ErrorList { + if newInt < 0 { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", name), name)) } + return allErrs } -func EnsureEqualMapStringString(new *map[string]string, old *map[string]string, name string, allErrs field.ErrorList) field.ErrorList { - if old == nil && new == nil { +func EnsureEqualMapStringString(newMap map[string]string, oldMap map[string]string, name string, allErrs field.ErrorList) field.ErrorList { + if oldMap == nil && newMap == nil { return allErrs } - if new == nil || old == nil { + if newMap == nil || oldMap == nil { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", name), name)) } - if !reflect.DeepEqual(old, new) { + if !reflect.DeepEqual(oldMap, newMap) { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", name), name)) } + return allErrs }