Skip to content

Commit f2257db

Browse files
authored
Merge pull request #31 from aws/feature/failure-domains
feature/failure domains
2 parents 465c0fa + 8c7c929 commit f2257db

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1571
-1794
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
*.dylib
77
bin
88
testbin/*
9+
api/*/zz*generated.deepcopy.go
10+
11+
nginx.conf
12+
nginx.conf.bak
913

1014
pkg/mocks/*
1115
!pkg/mocks/.keep

Makefile

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,12 @@ api/%/zz_generated.deepcopy.go: bin/controller-gen $(DEEPCOPY_GEN_INPUTS)
7474

7575
MANAGER_BIN_INPUTS=$(shell find ./controllers ./api ./pkg -name "*mock*" -prune -o -name "*test*" -prune -o -type f -print) main.go go.mod go.sum
7676
.PHONY: build
77-
build: binaries generate-mocks generate-deepcopy manifests release-manifests ## Build manager binary.
77+
build: binaries generate-deepcopy lint manifests release-manifests ## Build manager binary.
7878
bin/manager: $(MANAGER_BIN_INPUTS)
79-
go fmt ./...
80-
go vet ./...
8179
go build -o bin/manager main.go
8280

8381
.PHONY: run
84-
run: generate-deepcopy fmt vet ## Run a controller from your host.
85-
go fmt ./...
86-
go vet ./...
82+
run: generate-deepcopy ## Run a controller from your host.
8783
go run ./main.go
8884

8985
# Using a flag file here as docker build doesn't produce a target file.
@@ -163,14 +159,14 @@ clean: ## Clean.
163159
export KUBEBUILDER_ASSETS=$(PROJECT_DIR)/bin
164160

165161
.PHONY: test
166-
test: generate-mocks lint generate-deepcopy bin/ginkgo bin/kubectl bin/kube-apiserver bin/etcd ## Run tests. At the moment this is only unit tests.
162+
test: generate-mocks lint bin/ginkgo bin/kubectl bin/kube-apiserver bin/etcd ## Run tests. At the moment this is only unit tests.
167163
@./hack/testing_ginkgo_recover_statements.sh --add # Add ginkgo.GinkgoRecover() statements to controllers.
168164
@# The following is a slightly funky way to make sure the ginkgo statements are removed regardless the test results.
169165
@ginkgo -v ./api/... ./controllers/... ./pkg/... -coverprofile cover.out; EXIT_STATUS=$$?;\
170166
./hack/testing_ginkgo_recover_statements.sh --remove; exit $$EXIT_STATUS
171167

172168
.PHONY: generate-mocks
173-
generate-mocks: bin/mockgen pkg/mocks/mock_client.go $(shell find ./pkg/mocks -type f -name "mock*.go") ## Generate mocks needed for testing. Primarily mocks of the cloud package.
169+
generate-mocks: bin/mockgen generate-deepcopy pkg/mocks/mock_client.go $(shell find ./pkg/mocks -type f -name "mock*.go") ## Generate mocks needed for testing. Primarily mocks of the cloud package.
174170
pkg/mocks/mock%.go: $(shell find ./pkg/cloud -type f -name "*test*" -prune -o -print)
175171
go generate ./...
176172

api/v1beta1/cloudstackcluster_types.go

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,58 @@ type CloudStackIdentityReference struct {
3939
Name string `json:"name"`
4040
}
4141

42+
type Network struct {
43+
// Cloudstack Network ID the cluster is built in.
44+
// +optional
45+
ID string `json:"id,omitempty"`
46+
47+
// Cloudstack Network Type the cluster is built in.
48+
// + optional
49+
Type string `json:"type,omitempty"`
50+
51+
// Cloudstack Network Name the cluster is built in.
52+
// +optional
53+
Name string `json:"name"`
54+
}
55+
56+
type ZoneStatusMap map[string]Zone
57+
58+
// GetOne just returns a Zone from the map of zone statuses
59+
// Needed as there's no short way to do this.
60+
func (zones ZoneStatusMap) GetOne() *Zone {
61+
for _, zone := range zones {
62+
return &zone
63+
}
64+
return nil
65+
}
66+
67+
// GetByName fetches a zone by name if present in the map of zone statuses.
68+
// Needed as there's no short way to do this.
69+
func (zones ZoneStatusMap) GetByName(name string) *Zone {
70+
for zoneName, zone := range zones {
71+
if zoneName == name {
72+
return &zone
73+
}
74+
}
75+
return nil
76+
}
77+
78+
type Zone struct {
79+
// The Zone name.
80+
// + optional
81+
Name string `json:"name,omitempty"`
82+
83+
// The CS zone ID the cluster is built in.
84+
// + optional
85+
ID string `json:"id,omitempty"`
86+
87+
// The network within the Zone to use.
88+
Network Network `json:"network"`
89+
}
90+
4291
// CloudStackClusterSpec defines the desired state of CloudStackCluster.
4392
type CloudStackClusterSpec struct {
44-
// CloudStack Zone name.
45-
Zone string `json:"zone"`
46-
47-
// CloudStack guest network name.
48-
Network string `json:"network,omitempty"`
93+
Zones []Zone `json:"zones"`
4994

5095
// The kubernetes control plane endpoint.
5196
ControlPlaneEndpoint clusterv1.APIEndpoint `json:"controlPlaneEndpoint"`
@@ -65,24 +110,28 @@ type CloudStackClusterSpec struct {
65110

66111
// The status of the abstract CS k8s (not an actual Cloudstack Cluster) cluster.
67112
type CloudStackClusterStatus struct {
68-
// Reflects the readiness of the CS cluster.
69-
Ready bool `json:"ready"`
70113

71-
// The CS zone ID the cluster is built in.
72-
ZoneID string `json:"zoneID"`
114+
// The status of the cluster's ACS Zones.
115+
// +optional
116+
Zones ZoneStatusMap `json:"zones,omitempty"`
73117

74-
// Cloudstack Network ID the cluster is built in.
75-
NetworkID string `json:"networkID,omitempty"`
118+
// CAPI recognizes failure domains as a method to spread machines.
119+
// CAPC sets failure domains to indicate functioning Zones.
120+
// +optional
121+
FailureDomains clusterv1.FailureDomains `json:"failureDomains,omitempty"`
76122

77-
// Cloudstack Network Type the cluster is built in.
78-
NetworkType string `json:"networkType,omitempty"`
123+
// Reflects the readiness of the CS cluster.
124+
Ready bool `json:"ready"`
79125

80126
// Cloudstack Domain ID the cluster is built in.
81127
DomainID string `json:"domainID,omitempty"`
82128

83129
// The CS public IP ID to use for the k8s endpoint.
84130
PublicIPID string `json:"publicIPID,omitempty"`
85131

132+
// The ID of the network the PublicIP is in.
133+
PublicIPNetworkID string `json:"publicIPNetworkID,omitempty"`
134+
86135
// The ID of the lb rule used to assign VMs to the lb.
87136
LBRuleID string `json:"loadBalancerRuleID,omitempty"`
88137
}

api/v1beta1/cloudstackcluster_webhook.go

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package v1beta1
1818

1919
import (
2020
"fmt"
21+
"reflect"
2122

2223
"github.com/aws/cluster-api-provider-cloudstack/pkg/webhookutil"
2324
"k8s.io/apimachinery/pkg/api/errors"
@@ -43,7 +44,7 @@ var _ webhook.Defaulter = &CloudStackCluster{}
4344

4445
// Default implements webhook.Defaulter so a webhook will be registered for the type
4546
func (r *CloudStackCluster) Default() {
46-
cloudstackclusterlog.Info("default", "name", r.Name)
47+
cloudstackclusterlog.V(1).Info("entered api default setting webhook", "api resource name", r.Name)
4748
// No defaulted values supported yet.
4849
}
4950

@@ -53,7 +54,7 @@ var _ webhook.Validator = &CloudStackCluster{}
5354

5455
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
5556
func (r *CloudStackCluster) ValidateCreate() error {
56-
cloudstackclusterlog.Info("validate create", "name", r.Name)
57+
cloudstackclusterlog.V(1).Info("entered validate create webhook", "api resource name", r.Name)
5758

5859
var errorList field.ErrorList
5960

@@ -63,23 +64,31 @@ func (r *CloudStackCluster) ValidateCreate() error {
6364
}
6465

6566
if (r.Spec.Account != "") && (r.Spec.Domain == "") {
66-
errorList = append(errorList, field.Required(field.NewPath("spec", "account"), "specifying account requires additionally specifying domain"))
67+
errorList = append(errorList, field.Required(
68+
field.NewPath("spec", "account"), "specifying account requires additionally specifying domain"))
6769
}
6870

69-
// Zone and Network are required fields
70-
errorList = webhookutil.EnsureFieldExists(r.Spec.Zone, "Zone", errorList)
71-
errorList = webhookutil.EnsureFieldExists(r.Spec.Network, "Network", errorList)
71+
// Require Zones and their respective Networks.
72+
if len(r.Spec.Zones) <= 0 {
73+
errorList = append(errorList, field.Required(field.NewPath("spec", "Zones"), "Zones"))
74+
} else {
75+
for _, zone := range r.Spec.Zones {
76+
if zone.Network.Name == "" && zone.Network.ID == "" {
77+
errorList = append(errorList, field.Required(
78+
field.NewPath("spec", "Zones", "Network"), "each Zone requires a Network specification"))
79+
}
80+
}
81+
}
7282

7383
return webhookutil.AggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, errorList)
7484
}
7585

7686
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
7787
func (r *CloudStackCluster) ValidateUpdate(old runtime.Object) error {
78-
cloudstackclusterlog.Info("validate update", "name", r.Name)
88+
cloudstackclusterlog.V(1).Info("entered validate update webhook", "api resource name", r.Name)
7989

8090
var (
81-
errorList field.ErrorList
82-
spec = r.Spec
91+
spec = r.Spec
8392
)
8493

8594
oldCluster, ok := old.(*CloudStackCluster)
@@ -88,31 +97,37 @@ func (r *CloudStackCluster) ValidateUpdate(old runtime.Object) error {
8897
}
8998
oldSpec := oldCluster.Spec
9099

91-
// IdentityRefs must be Secrets.
92-
if spec.IdentityRef != nil && spec.IdentityRef.Kind != defaultIdentityRefKind {
93-
errorList = append(errorList, field.Forbidden(field.NewPath("spec", "identityRef", "kind"), "must be a Secret"))
100+
// No spec fields may be updated.
101+
errorList := field.ErrorList(nil)
102+
if !reflect.DeepEqual(oldSpec.Zones, spec.Zones) {
103+
errorList = append(errorList, field.Forbidden(
104+
field.NewPath("spec", "Zones"), "Zones and sub-attributes may not be modified after creation"))
94105
}
95-
96-
// No spec fields may be changed
97-
errorList = webhookutil.EnsureStringFieldsAreEqual(spec.Zone, oldSpec.Zone, "zone", errorList)
98-
errorList = webhookutil.EnsureStringFieldsAreEqual(spec.Network, oldSpec.Network, "network", errorList)
99106
if oldSpec.ControlPlaneEndpoint.Host != "" { // Need to allow one time endpoint setting via CAPC cluster controller.
100107
errorList = webhookutil.EnsureStringFieldsAreEqual(
101-
spec.ControlPlaneEndpoint.Host, oldSpec.ControlPlaneEndpoint.Host, "controlplaneendpointhost", errorList)
108+
spec.ControlPlaneEndpoint.Host, oldSpec.ControlPlaneEndpoint.Host, "controlplaneendpoint.host", errorList)
102109
errorList = webhookutil.EnsureStringFieldsAreEqual(
103-
string(spec.ControlPlaneEndpoint.Port), string(oldSpec.ControlPlaneEndpoint.Port), "controlplaneendpointport", errorList)
110+
string(spec.ControlPlaneEndpoint.Port), string(oldSpec.ControlPlaneEndpoint.Port),
111+
"controlplaneendpoint.port", errorList)
104112
}
105113
if spec.IdentityRef != nil && oldSpec.IdentityRef != nil {
106-
errorList = webhookutil.EnsureStringFieldsAreEqual(spec.IdentityRef.Kind, oldSpec.IdentityRef.Kind, "identityRef.Kind", errorList)
107-
errorList = webhookutil.EnsureStringFieldsAreEqual(spec.IdentityRef.Name, oldSpec.IdentityRef.Name, "identityRef.Name", errorList)
114+
errorList = webhookutil.EnsureStringFieldsAreEqual(
115+
spec.IdentityRef.Kind, oldSpec.IdentityRef.Kind, "identityref.kind", errorList)
116+
errorList = webhookutil.EnsureStringFieldsAreEqual(spec.IdentityRef.Name, oldSpec.IdentityRef.Name,
117+
"identityref.name", errorList)
118+
}
119+
120+
// IdentityRefs must be Secrets.
121+
if spec.IdentityRef != nil && spec.IdentityRef.Kind != defaultIdentityRefKind {
122+
errorList = append(errorList, field.Forbidden(field.NewPath("spec", "identityRef", "kind"), "must be a Secret"))
108123
}
109124

110125
return webhookutil.AggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, errorList)
111126
}
112127

113128
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
114129
func (r *CloudStackCluster) ValidateDelete() error {
115-
cloudstackclusterlog.Info("validate delete", "name", r.Name)
130+
cloudstackclusterlog.V(1).Info("entered validate delete webhook", "api resource name", r.Name)
116131
// No deletion validations. Deletion webhook not enabled.
117132
return nil
118133
}

0 commit comments

Comments
 (0)