@@ -18,7 +18,6 @@ package v1beta2
18
18
19
19
import (
20
20
"fmt"
21
- "reflect"
22
21
23
22
"k8s.io/apimachinery/pkg/api/errors"
24
23
"k8s.io/apimachinery/pkg/runtime"
@@ -94,12 +93,12 @@ func (r *CloudStackCluster) ValidateUpdate(old runtime.Object) error {
94
93
}
95
94
oldSpec := oldCluster .Spec
96
95
97
- // No spec fields may be updated.
98
96
errorList := field .ErrorList (nil )
99
- if ! reflect . DeepEqual ( oldSpec . FailureDomains , spec . FailureDomains ) {
100
- errorList = append ( errorList , field . Forbidden (
101
- field . NewPath ( "spec" , "FailureDomains" ), "FailureDomains and sub-attributes may not be modified after creation" ) )
97
+
98
+ if err := ValidateFailureDomainUpdates ( oldSpec . FailureDomains , spec . FailureDomains ); err != nil {
99
+ errorList = append ( errorList , err )
102
100
}
101
+
103
102
if oldSpec .ControlPlaneEndpoint .Host != "" { // Need to allow one time endpoint setting via CAPC cluster controller.
104
103
errorList = webhookutil .EnsureStringFieldsAreEqual (
105
104
spec .ControlPlaneEndpoint .Host , oldSpec .ControlPlaneEndpoint .Host , "controlplaneendpoint.host" , errorList )
@@ -111,6 +110,43 @@ func (r *CloudStackCluster) ValidateUpdate(old runtime.Object) error {
111
110
return webhookutil .AggregateObjErrors (r .GroupVersionKind ().GroupKind (), r .Name , errorList )
112
111
}
113
112
113
+ // ValidateFailureDomainUpdates verifies that at least one failure domain has not been deleted, and
114
+ // failure domains that are held over have not been modified.
115
+ func ValidateFailureDomainUpdates (oldFDs , newFDs []CloudStackFailureDomainSpec ) * field.Error {
116
+ newFDsByName := map [string ]CloudStackFailureDomainSpec {}
117
+ for _ , newFD := range newFDs {
118
+ newFDsByName [newFD .Name ] = newFD
119
+ }
120
+
121
+ atLeastOneRemains := false
122
+ for _ , oldFD := range oldFDs {
123
+ if newFD , present := newFDsByName [oldFD .Name ]; present {
124
+ atLeastOneRemains = true
125
+ if ! FailureDomainsEqual (newFD , oldFD ) {
126
+ return field .Forbidden (field .NewPath ("spec" , "FailureDomains" ),
127
+ fmt .Sprintf ("Cannot change FailureDomain %s" , oldFD .Name ))
128
+ }
129
+ }
130
+ }
131
+ if ! atLeastOneRemains {
132
+ return field .Forbidden (field .NewPath ("spec" , "FailureDomains" ), "At least one FailureDomain must be unchanged on udpate." )
133
+ }
134
+ return nil
135
+ }
136
+
137
+ // FailureDomainsEqual is a manual deep equal on failure domains.
138
+ func FailureDomainsEqual (fd1 , fd2 CloudStackFailureDomainSpec ) bool {
139
+ return fd1 .Name == fd2 .Name &&
140
+ fd1 .ACSEndpoint == fd2 .ACSEndpoint &&
141
+ fd1 .Account == fd2 .Account &&
142
+ fd1 .Domain == fd2 .Domain &&
143
+ fd1 .Zone .Name == fd2 .Zone .Name &&
144
+ fd1 .Zone .ID == fd2 .Zone .ID &&
145
+ fd1 .Zone .Network .Name == fd2 .Zone .Network .Name &&
146
+ fd1 .Zone .Network .ID == fd2 .Zone .Network .ID &&
147
+ fd1 .Zone .Network .Type == fd2 .Zone .Network .Type
148
+ }
149
+
114
150
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
115
151
func (r * CloudStackCluster ) ValidateDelete () error {
116
152
cloudstackclusterlog .V (1 ).Info ("entered validate delete webhook" , "api resource name" , r .Name )
0 commit comments