Skip to content

Commit a7ebf5e

Browse files
Merge pull request #1463 from abays/osversion_check
[OSPRH-17106] Improve detection of pre-existing OpenStackVersion
2 parents d4373be + 222db14 commit a7ebf5e

File tree

18 files changed

+238
-11
lines changed

18 files changed

+238
-11
lines changed

apis/core/v1beta1/conditions.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ const (
152152

153153
// OpenStackControlPlaneCertCleanupReadyCondition Status=True condition which indicates global certification cleanup is Ready
154154
OpenStackControlPlaneCertCleanupReadyCondition condition.Type = "OpenStackControlPlaneCertCleanupReadyCondition"
155+
156+
// OpenStackControlPlaneOpenStackVersionInitializationReadyCondition Status=True condition which indicates if OpenStackVersion is initialized
157+
OpenStackControlPlaneOpenStackVersionInitializationReadyCondition condition.Type = "OpenStackControlPlaneOpenStackVersionInitializationReadyCondition"
155158
)
156159

157160
// Common Messages used by API objects.
@@ -471,6 +474,18 @@ const (
471474

472475
// OpenStackControlPlaneInstanceHaCMReadyMessage
473476
OpenStackControlPlaneInstanceHaCMReadyMessage = "OpenStackControlPlane InstanceHa CM is available"
477+
478+
// OpenStackControlPlaneOpenStackVersionInitializationReadyInitMessage
479+
OpenStackControlPlaneOpenStackVersionInitializationReadyInitMessage = "OpenStackControlPlane OpenStackVersion initialization not started"
480+
481+
// OpenStackControlPlaneOpenStackVersionInitializationReadyMessage
482+
OpenStackControlPlaneOpenStackVersionInitializationReadyMessage = "OpenStackControlPlane OpenStackVersion initialized"
483+
484+
// OpenStackControlPlaneOpenStackVersionInitializationReadyRunningMessage
485+
OpenStackControlPlaneOpenStackVersionInitializationReadyRunningMessage = "OpenStackControlPlane OpenStackVersion initialization in progress"
486+
487+
// OpenStackControlPlaneOpenStackVersionInitializationReadyErrorMessage
488+
OpenStackControlPlaneOpenStackVersionInitializationReadyErrorMessage = "OpenStackControlPlane OpenStackVersion initialization error occured %s"
474489
)
475490

476491
// Version Conditions used by to drive minor updates

apis/core/v1beta1/openstackcontrolplane_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,7 @@ func (instance *OpenStackControlPlane) InitConditions() {
912912
condition.UnknownCondition(OpenStackControlPlaneBarbicanReadyCondition, condition.InitReason, OpenStackControlPlaneBarbicanReadyInitMessage),
913913
condition.UnknownCondition(OpenStackControlPlaneRedisReadyCondition, condition.InitReason, OpenStackControlPlaneRedisReadyInitMessage),
914914
condition.UnknownCondition(OpenStackControlPlaneCAReadyCondition, condition.InitReason, OpenStackControlPlaneCAReadyInitMessage),
915+
condition.UnknownCondition(OpenStackControlPlaneOpenStackVersionInitializationReadyCondition, condition.InitReason, OpenStackControlPlaneOpenStackVersionInitializationReadyInitMessage),
915916

916917
// Also add the overall status condition as Unknown
917918
condition.UnknownCondition(condition.ReadyCondition, condition.InitReason, condition.ReadyInitMessage),

apis/core/v1beta1/openstackcontrolplane_webhook.go

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ var _ webhook.Validator = &OpenStackControlPlane{}
8383
func (r *OpenStackControlPlane) ValidateCreate() (admission.Warnings, error) {
8484
openstackcontrolplanelog.Info("validate create", "name", r.Name)
8585

86-
var allErrs field.ErrorList
8786
var allWarn []string
8887
basePath := field.NewPath("spec")
8988

@@ -118,7 +117,15 @@ func (r *OpenStackControlPlane) ValidateCreate() (admission.Warnings, error) {
118117
)
119118
}
120119

121-
allWarn, allErrs = r.ValidateCreateServices(basePath)
120+
allErrs, err := r.ValidateVersion()
121+
122+
// Version validation can generate non-field errors, so we consider those first
123+
if err != nil {
124+
return nil, err
125+
}
126+
127+
allWarn, errs := r.ValidateCreateServices(basePath)
128+
allErrs = append(allErrs, errs...)
122129

123130
if err := r.ValidateTopology(basePath); err != nil {
124131
allErrs = append(allErrs, err)
@@ -672,6 +679,56 @@ func (r *OpenStackControlPlane) ValidateServiceDependencies(basePath *field.Path
672679
return allErrs
673680
}
674681

682+
func (r *OpenStackControlPlane) ValidateVersion() (field.ErrorList, error) {
683+
var allErrs field.ErrorList
684+
685+
openStackVersionList, err := GetOpenStackVersions(r.Namespace, ctlplaneWebhookClient)
686+
687+
if err != nil {
688+
return allErrs, apierrors.NewForbidden(
689+
schema.GroupResource{
690+
Group: GroupVersion.WithKind("OpenStackControlPlane").Group,
691+
Resource: GroupVersion.WithKind("OpenStackControlPlane").Kind,
692+
}, r.GetName(), &field.Error{
693+
Type: field.ErrorTypeForbidden,
694+
Field: "",
695+
BadValue: r.Name,
696+
Detail: err.Error(),
697+
},
698+
)
699+
}
700+
701+
if len(openStackVersionList.Items) > 0 {
702+
if len(openStackVersionList.Items) > 1 {
703+
return allErrs, apierrors.NewForbidden(
704+
schema.GroupResource{
705+
Group: GroupVersion.WithKind("OpenStackControlPlane").Group,
706+
Resource: GroupVersion.WithKind("OpenStackControlPlane").Kind,
707+
}, r.GetName(), &field.Error{
708+
Type: field.ErrorTypeForbidden,
709+
Field: "",
710+
BadValue: r.Name,
711+
Detail: fmt.Sprintf(
712+
"multiple (%d) OpenStackVersions found in namespace %s: only one may be present. Please rectify before creating OpenStackControlPlane",
713+
len(openStackVersionList.Items), r.Namespace),
714+
},
715+
)
716+
717+
}
718+
719+
openStackVersion := openStackVersionList.Items[0]
720+
721+
if openStackVersion.Name != r.Name {
722+
err := field.Invalid(field.NewPath("metadata").Child("name"),
723+
r.Name, fmt.Sprintf("OpenStackControlPlane '%s' must have same name as the existing '%s' OpenStackVersion",
724+
r.Name, openStackVersion.Name))
725+
allErrs = append(allErrs, err)
726+
}
727+
}
728+
729+
return allErrs, nil
730+
}
731+
675732
// +kubebuilder:webhook:path=/mutate-core-openstack-org-v1beta1-openstackcontrolplane,mutating=true,failurePolicy=fail,sideEffects=None,groups=core.openstack.org,resources=openstackcontrolplanes,verbs=create;update,versions=v1beta1,name=mopenstackcontrolplane.kb.io,admissionReviewVersions=v1
676733

677734
var _ webhook.Defaulter = &OpenStackControlPlane{}

apis/core/v1beta1/openstackversion_types.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
20+
"context"
2021
"regexp"
2122

2223
condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition"
2324
"github.com/openstack-k8s-operators/lib-common/modules/common/util"
2425
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
2527
)
2628

2729
const (
@@ -266,3 +268,18 @@ func GetOpenStackReleaseVersion(envVars []string) string {
266268
)
267269

268270
}
271+
272+
// GetOpenStackVersions - returns the OpenStackVersion resource(s) associated with the namespace
273+
func GetOpenStackVersions(namespace string, k8sClient client.Client) (*OpenStackVersionList, error) {
274+
versionList := &OpenStackVersionList{}
275+
276+
listOpts := []client.ListOption{
277+
client.InNamespace(namespace),
278+
}
279+
280+
if err := k8sClient.List(context.TODO(), versionList, listOpts...); err != nil {
281+
return nil, err
282+
}
283+
284+
return versionList, nil
285+
}

apis/core/v1beta1/openstackversion_webhook.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
20-
"context"
2120
"os"
2221

2322
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -26,7 +25,6 @@ import (
2625

2726
"k8s.io/apimachinery/pkg/runtime"
2827
ctrl "sigs.k8s.io/controller-runtime"
29-
"sigs.k8s.io/controller-runtime/pkg/client"
3028
goClient "sigs.k8s.io/controller-runtime/pkg/client"
3129
logf "sigs.k8s.io/controller-runtime/pkg/log"
3230
"sigs.k8s.io/controller-runtime/pkg/webhook"
@@ -100,13 +98,9 @@ func (r *OpenStackVersion) ValidateCreate() (admission.Warnings, error) {
10098
)
10199
}
102100

103-
versionList := &OpenStackVersionList{}
101+
versionList, err := GetOpenStackVersions(r.Namespace, versionWebhookClient)
104102

105-
listOpts := []client.ListOption{
106-
client.InNamespace(r.Namespace),
107-
}
108-
109-
if err := versionWebhookClient.List(context.TODO(), versionList, listOpts...); err != nil {
103+
if err != nil {
110104

111105
return nil, apierrors.NewForbidden(
112106
schema.GroupResource{

controllers/core/openstackcontrolplane_controller.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,21 @@ func (r *OpenStackControlPlaneReconciler) Reconcile(ctx context.Context, req ctr
203203
Log.Info("Looking up the current OpenStackVersion")
204204
ctrlResult, version, err := openstack.ReconcileVersion(ctx, instance, helper)
205205
if err != nil {
206+
instance.Status.Conditions.Set(condition.FalseCondition(
207+
corev1beta1.OpenStackControlPlaneOpenStackVersionInitializationReadyCondition,
208+
condition.ErrorReason,
209+
condition.SeverityWarning,
210+
corev1beta1.OpenStackControlPlaneOpenStackVersionInitializationReadyErrorMessage,
211+
err.Error()))
206212
return ctrl.Result{}, err
207213
} else if (ctrlResult != ctrl.Result{}) {
214+
// It appears that openstack.ReconcileVersion never returns a non-empty ctrlResult,
215+
// but we'll put this condition update here just in case it ever changes
216+
instance.Status.Conditions.Set(condition.FalseCondition(
217+
corev1beta1.OpenStackControlPlaneOpenStackVersionInitializationReadyCondition,
218+
condition.RequestedReason,
219+
condition.SeverityInfo,
220+
corev1beta1.OpenStackControlPlaneOpenStackVersionInitializationReadyRunningMessage))
208221
return ctrlResult, nil
209222
}
210223

@@ -216,7 +229,13 @@ func (r *OpenStackControlPlaneReconciler) Reconcile(ctx context.Context, req ctr
216229
Log,
217230
)
218231
if err != nil {
219-
Log.Error(err, "unable to create helper")
232+
Log.Error(err, "unable to create version helper")
233+
instance.Status.Conditions.Set(condition.FalseCondition(
234+
corev1beta1.OpenStackControlPlaneOpenStackVersionInitializationReadyCondition,
235+
condition.ErrorReason,
236+
condition.SeverityWarning,
237+
corev1beta1.OpenStackControlPlaneOpenStackVersionInitializationReadyErrorMessage,
238+
err.Error()))
220239
return ctrl.Result{}, err
221240
}
222241

@@ -227,9 +246,16 @@ func (r *OpenStackControlPlaneReconciler) Reconcile(ctx context.Context, req ctr
227246

228247
// wait until the version is initialized so we have images on the version.Status
229248
if !version.Status.Conditions.IsTrue(corev1beta1.OpenStackVersionInitialized) {
249+
instance.Status.Conditions.Set(condition.FalseCondition(
250+
corev1beta1.OpenStackControlPlaneOpenStackVersionInitializationReadyCondition,
251+
condition.RequestedReason,
252+
condition.SeverityInfo,
253+
corev1beta1.OpenStackControlPlaneOpenStackVersionInitializationReadyRunningMessage))
230254
return ctrlResult, nil
231255
}
232256

257+
instance.Status.Conditions.MarkTrue(corev1beta1.OpenStackControlPlaneOpenStackVersionInitializationReadyCondition, corev1beta1.OpenStackControlPlaneOpenStackVersionInitializationReadyMessage)
258+
233259
if instance.Status.DeployedVersion == nil || version.Spec.TargetVersion == *instance.Status.DeployedVersion { //revive:disable:indent-error-flow
234260
// green field deployment or no minor update in progress
235261
ctrlResult, err := r.reconcileNormal(ctx, instance, version, helper)

tests/functional/ctlplane/base_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type Names struct {
4747
Namespace string
4848
OpenStackControlplaneName types.NamespacedName
4949
OpenStackVersionName types.NamespacedName
50+
OpenStackVersionName2 types.NamespacedName
5051
KeystoneAPIName types.NamespacedName
5152
MemcachedName types.NamespacedName
5253
MemcachedCertName types.NamespacedName
@@ -98,6 +99,10 @@ func CreateNames(openstackControlplaneName types.NamespacedName) Names {
9899
Namespace: openstackControlplaneName.Namespace,
99100
Name: openstackControlplaneName.Name, // same name as controlplane
100101
},
102+
OpenStackVersionName2: types.NamespacedName{
103+
Namespace: openstackControlplaneName.Namespace,
104+
Name: "arbitrary-openstackversion", // different name than controlplane
105+
},
101106
RootCAPublicName: types.NamespacedName{
102107
Namespace: openstackControlplaneName.Namespace,
103108
Name: "rootca-public"},

tests/functional/ctlplane/openstackoperator_controller_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1832,6 +1832,78 @@ var _ = Describe("OpenStackOperator controller", func() {
18321832
})
18331833
})
18341834

1835+
When("OpenStackControlplane instance is created and an OpenStackVersion already exists with an arbitrary name", func() {
1836+
BeforeEach(func() {
1837+
DeferCleanup(
1838+
th.DeleteInstance,
1839+
CreateOpenStackVersion(names.OpenStackVersionName2, GetDefaultOpenStackVersionSpec()),
1840+
)
1841+
})
1842+
1843+
It("rejects the OpenStackControlPlane if its name is not that same as the OpenStackVersion's name", func() {
1844+
raw := map[string]interface{}{
1845+
"apiVersion": "core.openstack.org/v1beta1",
1846+
"kind": "OpenStackControlPlane",
1847+
"metadata": map[string]interface{}{
1848+
"name": names.OpenStackControlplaneName.Name,
1849+
"namespace": names.Namespace,
1850+
},
1851+
"spec": GetDefaultOpenStackControlPlaneSpec(),
1852+
}
1853+
1854+
unstructuredObj := &unstructured.Unstructured{Object: raw}
1855+
_, err := controllerutil.CreateOrPatch(
1856+
th.Ctx, th.K8sClient, unstructuredObj, func() error { return nil })
1857+
Expect(err).Should(HaveOccurred())
1858+
var statusError *k8s_errors.StatusError
1859+
Expect(errors.As(err, &statusError)).To(BeTrue())
1860+
Expect(statusError.ErrStatus.Details.Kind).To(Equal("OpenStackControlPlane"))
1861+
Expect(statusError.ErrStatus.Message).To(
1862+
ContainSubstring(
1863+
"must have same name as the existing"),
1864+
)
1865+
1866+
// we remove the finalizer as this is needed when the OpenStackControlplane
1867+
// does not create the OpenStackVersion itself
1868+
DeferCleanup(
1869+
OpenStackVersionRemoveFinalizer,
1870+
ctx,
1871+
names.OpenStackVersionName2,
1872+
)
1873+
})
1874+
1875+
It("accepts the OpenStackControlPlane if its name is the same as the OpenStackVersion's name", func() {
1876+
raw := map[string]interface{}{
1877+
"apiVersion": "core.openstack.org/v1beta1",
1878+
"kind": "OpenStackControlPlane",
1879+
"metadata": map[string]interface{}{
1880+
"name": names.OpenStackVersionName2.Name,
1881+
"namespace": names.Namespace,
1882+
},
1883+
"spec": GetDefaultOpenStackControlPlaneSpec(),
1884+
}
1885+
1886+
unstructuredObj := &unstructured.Unstructured{Object: raw}
1887+
_, err := controllerutil.CreateOrPatch(
1888+
th.Ctx, th.K8sClient, unstructuredObj, func() error { return nil })
1889+
Expect(err).ShouldNot(HaveOccurred())
1890+
1891+
openStackControlPlane := &corev1.OpenStackControlPlane{}
1892+
openStackControlPlane.ObjectMeta.Namespace = names.Namespace
1893+
openStackControlPlane.Name = names.OpenStackVersionName2.Name
1894+
err = k8sClient.Delete(ctx, openStackControlPlane)
1895+
Expect(err).ShouldNot(HaveOccurred())
1896+
1897+
// we remove the finalizer as this is needed when the OpenStackControlplane
1898+
// does not create the OpenStackVersion itself
1899+
DeferCleanup(
1900+
OpenStackVersionRemoveFinalizer,
1901+
ctx,
1902+
names.OpenStackVersionName2,
1903+
)
1904+
})
1905+
})
1906+
18351907
When("An OpenStackControlplane instance is created with nodeSelector", func() {
18361908
BeforeEach(func() {
18371909
spec := GetDefaultOpenStackControlPlaneSpec()

tests/kuttl/common/assert-sample-deployment.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,10 @@ status:
282282
reason: Ready
283283
status: "True"
284284
type: OpenStackControlPlaneOVNReady
285+
- message: OpenStackControlPlane OpenStackVersion initialized
286+
reason: Ready
287+
status: "True"
288+
type: OpenStackControlPlaneOpenStackVersionInitializationReadyCondition
285289
- message: OpenStackControlPlane PlacementAPI completed
286290
reason: Ready
287291
status: "True"

tests/kuttl/tests/ctlplane-basic-deployment/03-assert-deploy-custom-cacert.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ status:
9191
reason: Ready
9292
status: "True"
9393
type: OpenStackControlPlaneOVNReady
94+
- message: OpenStackControlPlane OpenStackVersion initialized
95+
reason: Ready
96+
status: "True"
97+
type: OpenStackControlPlaneOpenStackVersionInitializationReadyCondition
9498
- message: OpenStackControlPlane PlacementAPI completed
9599
reason: Ready
96100
status: "True"

0 commit comments

Comments
 (0)