Skip to content

Commit a938ea4

Browse files
committed
cyborg: Implement Cyborg top-level controller reconcile loop
Add full reconcile logic for the Cyborg CR: - Manage RBAC resources (ServiceAccount, Role, RoleBinding) - Validate input password secret and RabbitMQ TransportURL secret - Create MariaDB database and run DB sync job via a batch Job - Register Cyborg service in Keystone - Create a sub-level secret aggregating DB credentials, transport URL and service password to be consumed by CyborgAPI and CyborgConductor - Track readiness via structured conditions on CyborgStatus - Add functional tests covering the full reconcile flow Assisted-By: Claude Signed-off-by: Alfredo Moralejo <amoralej@redhat.com>
1 parent 1f098c1 commit a938ea4

24 files changed

Lines changed: 3111 additions & 54 deletions

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,13 @@ docker-buildx: ## Build and push docker image for the manager for cross-platform
114114

115115
.PHONY: manifests
116116
manifests: gowork controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
117-
$(CONTROLLER_GEN) crd webhook paths="./api/nova/..." paths="./api/placement/..." paths="./internal/webhook/nova/..." paths="./internal/webhook/placement/..." output:crd:artifacts:config=config/crd/bases output:webhook:artifacts:config=config/webhook && \
117+
$(CONTROLLER_GEN) crd webhook paths="./api/nova/..." paths="./api/placement/..." paths="./api/cyborg/..." paths="./internal/webhook/nova/..." paths="./internal/webhook/placement/..." paths="./internal/webhook/cyborg/..." output:crd:artifacts:config=config/crd/bases output:webhook:artifacts:config=config/webhook && \
118118
$(CONTROLLER_GEN) rbac:roleName=manager-role paths="./..." output:dir=config/rbac && \
119119
rm -f api/bases/* && cp -a config/crd/bases api/
120120

121121
.PHONY: generate
122122
generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
123-
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./api/nova/..." paths="./api/placement/..."
123+
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./api/nova/..." paths="./api/placement/..." paths="./api/cyborg/..."
124124

125125
.PHONY: fmt
126126
fmt: ## Run go fmt against code.

api/bases/cyborg.openstack.org_cyborgs.yaml

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,7 @@ spec:
524524
e.g. to check logs
525525
type: boolean
526526
secret:
527+
default: osp-secret
527528
description: |-
528529
Secret is the name of the Secret instance containing password
529530
information for cyborg like the keystone service password and DB passwords
@@ -554,7 +555,6 @@ spec:
554555
- agentContainerImageURL
555556
- apiContainerImageURL
556557
- conductorContainerImageURL
557-
- secret
558558
type: object
559559
status:
560560
description: CyborgStatus defines the observed state of Cyborg.
@@ -564,6 +564,13 @@ spec:
564564
from cyborg-api
565565
format: int32
566566
type: integer
567+
applicationCredentialSecret:
568+
description: |-
569+
ApplicationCredentialSecret - the AC secret cyborg is currently
570+
consuming and protecting with the openstack.org/cyborg-ac-consumer
571+
finalizer. Tracked so the controller can remove its finalizer from the
572+
old secret when the openstack-operator rotates the reference.
573+
type: string
567574
conditions:
568575
description: Conditions
569576
items:
@@ -611,12 +618,22 @@ spec:
611618
ready from cyborg-conductor
612619
format: int32
613620
type: integer
621+
hash:
622+
additionalProperties:
623+
type: string
624+
description: Hash - Map of hashes to track e.g. job status
625+
type: object
614626
observedGeneration:
615-
description: ObservedGeneration - the most recent generation observed
616-
for this service. If the observed generation is less than the spec
617-
generation, then the controller has not processed the latest changes.
627+
description: |-
628+
ObservedGeneration - the most recent generation observed for this
629+
service. If the observed generation is less than the spec generation,
630+
then the controller has not processed the latest changes.
618631
format: int64
619632
type: integer
633+
serviceID:
634+
description: ServiceID - The ID of the cyborg service registered in
635+
keystone
636+
type: string
620637
type: object
621638
type: object
622639
served: true

api/cyborg/v1beta1/conditions.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
Copyright 2026.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1beta1
18+
19+
import "github.com/openstack-k8s-operators/lib-common/modules/common/condition"
20+
21+
const (
22+
// DbSyncHash hash
23+
DbSyncHash = "dbsync"
24+
)
25+
26+
const (
27+
// CyborgRabbitMQTransportURLReadyCondition indicates whether the Cyborg RabbitMQ TransportURL is ready
28+
CyborgRabbitMQTransportURLReadyCondition condition.Type = "CyborgRabbitMQTransportURLReady"
29+
30+
// CyborgAPIReadyCondition indicates whether the CyborgAPI is ready
31+
CyborgAPIReadyCondition condition.Type = "CyborgAPIReady"
32+
33+
// CyborgConductorReadyCondition indicates whether the CyborgConductor is ready
34+
CyborgConductorReadyCondition condition.Type = "CyborgConductorReady"
35+
)
36+
37+
const (
38+
// CyborgRabbitMQTransportURLReadyRunningMessage -
39+
CyborgRabbitMQTransportURLReadyRunningMessage = "CyborgRabbitMQTransportURL creation in progress"
40+
41+
// CyborgRabbitMQTransportURLReadyMessage -
42+
CyborgRabbitMQTransportURLReadyMessage = "CyborgRabbitMQTransportURL successfully created"
43+
44+
// CyborgRabbitMQTransportURLReadyErrorMessage -
45+
CyborgRabbitMQTransportURLReadyErrorMessage = "CyborgRabbitMQTransportURL error occurred %s"
46+
47+
// CyborgAPIReadyInitMessage -
48+
CyborgAPIReadyInitMessage = "CyborgAPI not started"
49+
50+
// CyborgConductorReadyInitMessage -
51+
CyborgConductorReadyInitMessage = "CyborgConductor not started"
52+
53+
// CyborgApplicationCredentialSecretErrorMessage -
54+
CyborgApplicationCredentialSecretErrorMessage = "Error with application credential secret"
55+
)

api/cyborg/v1beta1/cyborg_types.go

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ type CyborgSpecCore struct {
6464
// APITimeout for Route and Apache
6565
APITimeout *int `json:"apiTimeout"`
6666

67-
// +kubebuilder:validation:Required
67+
// +kubebuilder:validation:Optional
68+
// +kubebuilder:default=osp-secret
6869
// Secret is the name of the Secret instance containing password
6970
// information for cyborg like the keystone service password and DB passwords
7071
Secret *string `json:"secret"`
@@ -114,14 +115,28 @@ type CyborgStatus struct {
114115
// Conditions
115116
Conditions condition.Conditions `json:"conditions,omitempty" optional:"true"`
116117

118+
// ServiceID - The ID of the cyborg service registered in keystone
119+
ServiceID string `json:"serviceID,omitempty"`
120+
121+
// Hash - Map of hashes to track e.g. job status
122+
Hash map[string]string `json:"hash,omitempty"`
123+
117124
// APIServiceReadyCount defines the number or replicas ready from cyborg-api
118125
APIServiceReadyCount int32 `json:"apiServiceReadyCount,omitempty"`
119126

120127
// ConductorServiceReadyCount defines the number or replicas ready from cyborg-conductor
121128
ConductorServiceReadyCount int32 `json:"conductorServiceReadyCount,omitempty"`
122129

123-
//ObservedGeneration - the most recent generation observed for this service. If the observed generation is less than the spec generation, then the controller has not processed the latest changes.
130+
// ObservedGeneration - the most recent generation observed for this
131+
// service. If the observed generation is less than the spec generation,
132+
// then the controller has not processed the latest changes.
124133
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
134+
135+
// ApplicationCredentialSecret - the AC secret cyborg is currently
136+
// consuming and protecting with the openstack.org/cyborg-ac-consumer
137+
// finalizer. Tracked so the controller can remove its finalizer from the
138+
// old secret when the openstack-operator rotates the reference.
139+
ApplicationCredentialSecret string `json:"applicationCredentialSecret,omitempty"`
125140
}
126141

127142
// +kubebuilder:object:root=true
@@ -145,6 +160,26 @@ type CyborgList struct {
145160
Items []Cyborg `json:"items"`
146161
}
147162

163+
// RbacConditionsSet sets the conditions for the RBAC reconciliation
164+
func (instance Cyborg) RbacConditionsSet(c *condition.Condition) {
165+
instance.Status.Conditions.Set(c)
166+
}
167+
168+
// RbacNamespace returns the namespace
169+
func (instance Cyborg) RbacNamespace() string {
170+
return instance.Namespace
171+
}
172+
173+
// RbacResourceName returns the name to be used for RBAC objects (serviceaccount, role, rolebinding)
174+
func (instance Cyborg) RbacResourceName() string {
175+
return "cyborg-" + instance.Name
176+
}
177+
178+
// IsReady returns true if the ReadyCondition is true
179+
func (instance *Cyborg) IsReady() bool {
180+
return instance.Status.Conditions.IsTrue(condition.ReadyCondition)
181+
}
182+
148183
func init() {
149184
SchemeBuilder.Register(&Cyborg{}, &CyborgList{})
150185
}

api/cyborg/v1beta1/zz_generated.deepcopy.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/main.go

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import (
4141
cyborgcontroller "github.com/openstack-k8s-operators/nova-operator/internal/controller/cyborg"
4242
novacontroller "github.com/openstack-k8s-operators/nova-operator/internal/controller/nova"
4343
placementcontroller "github.com/openstack-k8s-operators/nova-operator/internal/controller/placement"
44+
cyborgwebhookv1beta1 "github.com/openstack-k8s-operators/nova-operator/internal/webhook/cyborg/v1beta1"
4445
webhookv1beta1 "github.com/openstack-k8s-operators/nova-operator/internal/webhook/nova/v1beta1"
4546
placementwebhookv1 "github.com/openstack-k8s-operators/nova-operator/internal/webhook/placement/v1beta1"
4647
// +kubebuilder:scaffold:imports
@@ -51,7 +52,7 @@ import (
5152
keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1"
5253
"github.com/openstack-k8s-operators/lib-common/modules/common/operator"
5354
mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1"
54-
cyborgv1beta1 "github.com/openstack-k8s-operators/nova-operator/api/cyborg/v1beta1"
55+
cyborgv1 "github.com/openstack-k8s-operators/nova-operator/api/cyborg/v1beta1"
5556
novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1"
5657
placementv1 "github.com/openstack-k8s-operators/nova-operator/api/placement/v1beta1"
5758
appsv1 "k8s.io/api/apps/v1"
@@ -77,7 +78,7 @@ func init() {
7778
utilruntime.Must(networkv1.AddToScheme(scheme))
7879
utilruntime.Must(memcachedv1.AddToScheme(scheme))
7980
utilruntime.Must(topologyv1.AddToScheme(scheme))
80-
utilruntime.Must(cyborgv1beta1.AddToScheme(scheme))
81+
utilruntime.Must(cyborgv1.AddToScheme(scheme))
8182
//+kubebuilder:scaffold:scheme
8283
}
8384

@@ -280,6 +281,16 @@ func main() {
280281
novav1.SetupDefaults()
281282
placementv1.SetupDefaults()
282283

284+
if os.Getenv("ENABLE_CYBORG") == "true" {
285+
cyborgreconcilers := cyborgcontroller.NewReconcilers(mgr, kclient)
286+
err = cyborgreconcilers.Setup(mgr, setupLog)
287+
if err != nil {
288+
setupLog.Error(err, "unable to create controller", "controller", "Cyborg")
289+
os.Exit(1)
290+
}
291+
cyborgv1.SetupDefaults()
292+
}
293+
283294
// nolint:goconst
284295
checker := healthz.Ping
285296
if os.Getenv("ENABLE_WEBHOOKS") != "false" {
@@ -321,30 +332,23 @@ func main() {
321332
setupLog.Error(err, "unable to create webhook", "webhook", "PlacementAPI")
322333
os.Exit(1)
323334
}
335+
if os.Getenv("ENABLE_CYBORG") == "true" {
336+
if err := cyborgwebhookv1beta1.SetupCyborgWebhookWithManager(mgr); err != nil {
337+
setupLog.Error(err, "unable to create webhook", "webhook", "Cyborg")
338+
os.Exit(1)
339+
}
340+
if err := cyborgwebhookv1beta1.SetupCyborgAPIWebhookWithManager(mgr); err != nil {
341+
setupLog.Error(err, "unable to create webhook", "webhook", "CyborgAPI")
342+
os.Exit(1)
343+
}
344+
if err := cyborgwebhookv1beta1.SetupCyborgConductorWebhookWithManager(mgr); err != nil {
345+
setupLog.Error(err, "unable to create webhook", "webhook", "CyborgConductor")
346+
os.Exit(1)
347+
}
348+
}
324349
checker = mgr.GetWebhookServer().StartedChecker()
325-
326-
}
327-
if err := (&cyborgcontroller.CyborgReconciler{
328-
Client: mgr.GetClient(),
329-
Scheme: mgr.GetScheme(),
330-
}).SetupWithManager(mgr); err != nil {
331-
setupLog.Error(err, "unable to create controller", "controller", "Cyborg")
332-
os.Exit(1)
333-
}
334-
if err := (&cyborgcontroller.CyborgAPIReconciler{
335-
Client: mgr.GetClient(),
336-
Scheme: mgr.GetScheme(),
337-
}).SetupWithManager(mgr); err != nil {
338-
setupLog.Error(err, "unable to create controller", "controller", "CyborgAPI")
339-
os.Exit(1)
340-
}
341-
if err := (&cyborgcontroller.CyborgConductorReconciler{
342-
Client: mgr.GetClient(),
343-
Scheme: mgr.GetScheme(),
344-
}).SetupWithManager(mgr); err != nil {
345-
setupLog.Error(err, "unable to create controller", "controller", "CyborgConductor")
346-
os.Exit(1)
347350
}
351+
348352
// +kubebuilder:scaffold:builder
349353

350354
if metricsCertWatcher != nil {

config/crd/bases/cyborg.openstack.org_cyborgs.yaml

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,7 @@ spec:
524524
e.g. to check logs
525525
type: boolean
526526
secret:
527+
default: osp-secret
527528
description: |-
528529
Secret is the name of the Secret instance containing password
529530
information for cyborg like the keystone service password and DB passwords
@@ -554,7 +555,6 @@ spec:
554555
- agentContainerImageURL
555556
- apiContainerImageURL
556557
- conductorContainerImageURL
557-
- secret
558558
type: object
559559
status:
560560
description: CyborgStatus defines the observed state of Cyborg.
@@ -564,6 +564,13 @@ spec:
564564
from cyborg-api
565565
format: int32
566566
type: integer
567+
applicationCredentialSecret:
568+
description: |-
569+
ApplicationCredentialSecret - the AC secret cyborg is currently
570+
consuming and protecting with the openstack.org/cyborg-ac-consumer
571+
finalizer. Tracked so the controller can remove its finalizer from the
572+
old secret when the openstack-operator rotates the reference.
573+
type: string
567574
conditions:
568575
description: Conditions
569576
items:
@@ -611,12 +618,22 @@ spec:
611618
ready from cyborg-conductor
612619
format: int32
613620
type: integer
621+
hash:
622+
additionalProperties:
623+
type: string
624+
description: Hash - Map of hashes to track e.g. job status
625+
type: object
614626
observedGeneration:
615-
description: ObservedGeneration - the most recent generation observed
616-
for this service. If the observed generation is less than the spec
617-
generation, then the controller has not processed the latest changes.
627+
description: |-
628+
ObservedGeneration - the most recent generation observed for this
629+
service. If the observed generation is less than the spec generation,
630+
then the controller has not processed the latest changes.
618631
format: int64
619632
type: integer
633+
serviceID:
634+
description: ServiceID - The ID of the cyborg service registered in
635+
keystone
636+
type: string
620637
type: object
621638
type: object
622639
served: true

0 commit comments

Comments
 (0)