Skip to content

Unify common code#1130

Open
amartyasinha wants to merge 8 commits into
openstack-k8s-operators:mainfrom
amartyasinha:unify_code
Open

Unify common code#1130
amartyasinha wants to merge 8 commits into
openstack-k8s-operators:mainfrom
amartyasinha:unify_code

Conversation

@amartyasinha

@amartyasinha amartyasinha commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Introduce internal/common for shared nova/placement
controller code: reconciler base, ensure helpers,
config generation, watch mappers, Kolla constants,
and static errors. Nova and placement controllers
call these helpers; nova-only logic stays in
internal/controller/nova/common.go.

This is a move-first refactor: logic, comments, and
naming are preserved where possible. Only
unavoidable mechanical changes are included (e.g.
exported names for cross-package use, import
updates, r.Client.Update because Client is a named
field on ReconcilerBase).

Co-authored-by: Cursor cursoragent@cursor.com

@openshift-ci

openshift-ci Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

}

// ReconcilerBase provides a common set of clients scheme and loggers for all reconcilers.
type ReconcilerBase struct {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so ReconcilerBase is intened to be the thing that hold the common code

so you shoudl be moving this as well if your going to do this.

the orginal inte was for this to become the common bas for the nova placment and evnetully cyborg contolers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @SeanMooney! Sure, I'll be moving ReconcilerBase soon to common code. I had it in the initial draft, but removed it because it was getting overwhelming for me to self-review. Once I believe the current code I'm moving are well-placed, I'll move ReconcilerBase as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried moving ReconcilerBase and other related func to internal/common. PTAL.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @SeanMooney! Sorry for the ping, but I'll appreciate if you could review the PR.

Comment thread internal/controller/placement/api_controller.go Outdated
Comment thread internal/controller/placement/api_controller.go
Comment thread internal/common/network.go Outdated
Comment thread internal/common/errors.go
@amartyasinha amartyasinha force-pushed the unify_code branch 2 times, most recently from fc8d155 to 7d82edc Compare June 23, 2026 11:34
@amartyasinha amartyasinha requested a review from mrkisaolamb June 23, 2026 11:38

@mrkisaolamb mrkisaolamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something else we can try: unifying FindObjectsForSrcByField and findObjectsWithAppSelectorLabelInNamespace

Comment thread internal/controller/placement/api_controller.go Outdated
Comment thread internal/controller/placement/api_controller.go Outdated
Comment thread internal/common/errors.go
Comment thread internal/controller/placement/api_controller.go
@amartyasinha amartyasinha force-pushed the unify_code branch 5 times, most recently from 2444cca to 3a8b6c6 Compare June 24, 2026 15:22
@amartyasinha amartyasinha marked this pull request as ready for review June 29, 2026 03:38
@openshift-ci openshift-ci Bot requested a review from amoralej June 29, 2026 03:38
@amartyasinha amartyasinha changed the title [WIP] Unify common code Unify common code Jun 29, 2026
Comment thread internal/common/errors.go Outdated
Comment thread internal/controller/nova/common.go Outdated
Comment thread internal/common/network.go
Comment thread internal/common/topology.go
@amartyasinha amartyasinha force-pushed the unify_code branch 4 times, most recently from 527e504 to 096833f Compare June 29, 2026 09:57
@amartyasinha amartyasinha requested a review from mrkisaolamb June 29, 2026 10:08

@mrkisaolamb mrkisaolamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good

@openshift-ci openshift-ci Bot added the lgtm label Jul 1, 2026
@openshift-ci

openshift-ci Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: amartyasinha, mrkisaolamb

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:
  • OWNERS [amartyasinha,mrkisaolamb]

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@mrkisaolamb mrkisaolamb removed the lgtm label Jul 1, 2026

@amoralej amoralej left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR looks mostly good to me. Some coments in line.

  • One of the intermediate commits does not build. Althoug the PR is fine, having unbuildable commits is undesired IMO.
  • The impelementation of the new common watcher may be simplified. I'm proposing an approach. Current implementation works.
  • The rest of comments are mostly nits.

Thanks for splitting the PR in commits, it'd be much harder to review otherwise!

Secret: &corev1.SecretVolumeSource{
DefaultMode: &configMode,
SecretName: name + "-config-data",
SecretName: internalcommon.GetServiceConfigSecretName(name),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This patch left hardcoded secret name in

Name: fmt.Sprintf("%s-config-data", instance.Name),
but it's cleaned later, when commonizing GenerateConfigs so no problem.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack! Will leave as it is since it got cleaned up later.


if !ok {
return fmt.Errorf("%w but got %T", errExpectedNovaAPIObject, obj)
return fmt.Errorf("expected a NovaAPI object but got %T: %w", obj, internalcommon.ErrUnexpectedObjectType)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is changing Error types from specific to generic ErrUnexpectedObjectType and slightly changing error text by adding unnexpected object type at the end. I haven't found any code checking error types and error message change seems fine..

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the reason was to keep things simple. So, we changed Error types from specific to generic, since we were not checking those specific Error type later in our code.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this and another couple of errors were left in commit e655c73 . This commit fails to build:

go vet ./...
# github.com/openstack-k8s-operators/nova-operator/internal/controller/placement
internal/controller/placement/api_controller.go:1019:12: r.List undefined (type *PlacementAPIReconciler has no field or method List)
internal/controller/placement/api_controller.go:1051:11: r.List undefined (type *PlacementAPIReconciler has no field or method List)
# github.com/openstack-k8s-operators/nova-operator/internal/controller/nova
internal/controller/nova/common.go:34:2: "k8s.io/apimachinery/pkg/runtime" imported and not used
make: *** [Makefile:131: vet] Error 1

While this has been fixed on the following commit in the PR, it's not a good practice, all commits should be buildable,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @amoralej ++ for catching this. Yes, I agree that all inidividual commits should be buildable. I was taking care of it, but seems like during the last regrouping of commits, it got messed up.

Anyways, it's been fixed now.

[AI-Generated]

Verified end to end. Results:


  Final tree unchanged

  ┌────────────────────┬──────────────────────────────────────────┐
  │ Reference          │ Tree hash                                │
  ├────────────────────┼──────────────────────────────────────────┤
  │ Old tip (096833f2) │ a780cab3467aefac63c0dbfd81d93dbeedb7c96d │
  ├────────────────────┼──────────────────────────────────────────┤
  │ New tip (2aaf127d) │ a780cab3467aefac63c0dbfd81d93dbeedb7c96d │
  └────────────────────┴──────────────────────────────────────────┘

  git diff 096833f2^{tree} HEAD^{tree} is empty — file content at the tip is identical to
  before the regroup. Only commit hashes changed.

  Every commit builds on its own

  Checked each of the 8 commits from 2085b0cf..HEAD individually:

  • go vet ./...
  • go vet ./api/...
  • go build -o /dev/null ./...
  • make vet (same as CI)

  All passed on every commit:

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
k8s_errors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be part of the previous commit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Namespace: src.GetNamespace(),
}
err := r.List(ctx, crList, listOps)
err := r.Client.List(ctx, crList, listOps)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be part of the previous patch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Thanks

Comment thread Makefile Outdated

.PHONY: gotest-unit
gotest-unit: ## Run unit tests under test/unit/.
go test ./test/unit/...

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding -v would be nice to make sure all tests are being executed.

go test -v ./test/unit/...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! done.

Comment thread Makefile Outdated
# TODO: Currently runs all tests (Nova + Placement). In future, optimize CI to run only tests
# for the operator code that changed (e.g., skip Placement tests if only Nova code changed).
test: manifests generate fmt vet envtest ginkgo ## Run tests.
test: manifests generate fmt vet envtest gotest-unit ginkgo ## Run tests.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We rely mostly in envtest functional test in service operators, but I think using unit tests for this common libraries is a good addition, thanks!

I think this is running unit tests twice as the ginkgo command below on ./test/ already runs these tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the catch! I've removed dedicated gotest-unit from test target, as ginkgo will already run them.

// placement uses EnsureSecret for the OpenStack password secret referenced in
// PlacementAPI.Spec.Secret with PasswordSelectors.Service defaulting to
// PlacementPassword.
func TestEnsureSecret_placementMissingSecret(t *testing.T) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: It only validates the NotFound case which has specific logic. For other errors in eader.Get(ctx, secretName, secret) it's does not have coverage.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added more coverage. PTAL

ctrl "sigs.k8s.io/controller-runtime"
)

func TestEnsureNetworkAttachments_noAttachments(t *testing.T) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: similar to ensureSecret, missing coverage for other errors than NotFound.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added more coverage. PTAL

Comment thread internal/common/watch.go Outdated
Comment on lines +40 to +41
newList func() L,
getItems func(L) S,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There may be a simpler way to simplify this by passing a single parameter NewList. AI proposes to use meta.ExtractList(crList) so that only the first parameter is needed. i.e.

 // FindObjectsForSrcByField returns reconcile requests for CRs in src's namespace
 // that reference src via one of the indexed watchFields.
-func FindObjectsForSrcByField[L client.ObjectList, E any, S ~[]E](
+func FindObjectsForSrcByField(
        ctx context.Context,
        log logr.Logger,
        reader client.Reader,
        src client.Object,
        watchFields []string,
-       newList func() L,
-       getItems func(L) S,
+       newList func() client.ObjectList,
 ) []reconcile.Request {
-       requests := []reconcile.Request{}
+       var requests []reconcile.Request
 
        for _, field := range watchFields {
                crList := newList()
@@ -54,23 +55,25 @@ func FindObjectsForSrcByField[L client.ObjectList, E any, S ~[]E](
                        return requests
                }
 
-               requests = appendRequestsForObjects(log, src, requests, itemsAsObjects(getItems(crList)))
+               items, err := meta.ExtractList(crList)
+               if err != nil {
+                       log.Error(err, fmt.Sprintf("extracting items from %s", crList.GetObjectKind().GroupVersionKind().Kind))
+                       return requests
+               }
+               requests = appendRequestsForObjects(log, src, requests, items)
        }
 
        return requests
 }
 

Additional changes are needed in appendRequestsForObjects.

 func appendRequestsForObjects(
        log logr.Logger,
        src client.Object,
        requests []reconcile.Request,
-       items []client.Object,
+       items []runtime.Object,
 ) []reconcile.Request {
        for _, item := range items {
-               log.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace()))
+               accessor, err := meta.Accessor(item)
+               if err != nil {
+                       log.Error(err, "extracting object metadata")
+                       continue
+               }
+               log.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), accessor.GetName(), accessor.GetNamespace()))
 
                requests = append(requests,
                        reconcile.Request{
                                NamespacedName: types.NamespacedName{
-                                       Name:      item.GetName(),
-                                       Namespace: item.GetNamespace(),
+                                       Name:      accessor.GetName(),
+                                       Namespace: accessor.GetNamespace(),
                                },
                        },
                )

Also, the rest of functions can follow the same approach. so that from services call is only:

+++ b/internal/controller/placement/api_controller.go
@@ -879,8 +879,7 @@ func (r *PlacementAPIReconciler) findObjectsForSrc(ctx context.Context, src clie
                r.Client,
                src,
                allWatchFields,
-               func() *placementv1.PlacementAPIList { return &placementv1.PlacementAPIList{} },
-               func(l *placementv1.PlacementAPIList) []placementv1.PlacementAPI { return l.Items },
+               func() client.ObjectList { return &placementv1.PlacementAPIList{} },
        )
 }

I'm attaching the entire change that AI proposed. At least this is passing the tests.

watchers.patch

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Thanks

@amartyasinha amartyasinha force-pushed the unify_code branch 2 times, most recently from 2aaf127 to e832d30 Compare July 3, 2026 04:27
…et names

Move shared Kolla volume and secret name helpers out of internal/nova so
nova and placement deployments can reuse them.
Move shared static errors into internal/common/errors.go and update webhook
and controller call sites to reference the shared definitions. For placement,
replace local ApplicationCredential error vars with the shared ones and wait
on a missing ApplicationCredential secret like nova does.
Extract ReconcilerBase, Manageable, Reconciler, NewReconcilerBase,
SetRequeueTimeout, Reconcilers, Setup, and OverrideRequeueTimeout into
internal/common. Wire nova, placement, and main to use the shared base.
Move EnsureSecret, EnsureNetworkAttachments, EnsureTopology, and
ConditionUpdater into internal/common. Switch nova and placement
controllers to use the shared helpers.
Remove the duplicate allSubConditionIsTrue helper from nova common; logic
matches condition.Conditions.AllSubConditionIsTrue() from lib-common.
Add unit tests for EnsureSecret, EnsureNetworkAttachments, and
EnsureTopology. Extend placement functional coverage for missing and
incomplete input secrets.
amartyasinha and others added 2 commits July 3, 2026 10:55
Replace duplicated controller watch map functions with generic helpers in
internal/common/watch.go.

Co-authored-by: Cursor <cursoragent@cursor.com>
Share config secret generation between nova and placement controllers.
Nova passes service-specific templates via novaAdditionalTemplates() in
nova/common.go; placement uses GenerateConfigsWithScripts in
generateServiceConfigMaps.

Co-authored-by: Cursor <cursoragent@cursor.com>
@centosinfra-prod-github-app

Copy link
Copy Markdown

Build failed (check pipeline). Post recheck (without leading slash)
to rerun all jobs. Make sure the failure cause has been resolved before
you rerun jobs.

https://gateway-cloud-softwarefactory.apps.ocp.cloud.ci.centos.org/zuul/t/rdoproject.org/buildset/22857439f9a5439d84d7ee32a45e0383

✔️ openstack-meta-content-provider SUCCESS in 2h 28m 20s
✔️ nova-operator-kuttl SUCCESS in 52m 05s
✔️ nova-operator-tempest-multinode SUCCESS in 2h 12m 02s
nova-operator-tempest-multinode-ceph FAILURE in 32m 28s

@amartyasinha

Copy link
Copy Markdown
Contributor Author

recheck

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants