Unify common code#1130
Conversation
|
Skipping CI for Draft Pull Request. |
6aab9a1 to
1ef05f9
Compare
| } | ||
|
|
||
| // ReconcilerBase provides a common set of clients scheme and loggers for all reconcilers. | ||
| type ReconcilerBase struct { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I've tried moving ReconcilerBase and other related func to internal/common. PTAL.
There was a problem hiding this comment.
Hey @SeanMooney! Sorry for the ping, but I'll appreciate if you could review the PR.
fc8d155 to
7d82edc
Compare
mrkisaolamb
left a comment
There was a problem hiding this comment.
Something else we can try: unifying FindObjectsForSrcByField and findObjectsWithAppSelectorLabelInNamespace
2444cca to
3a8b6c6
Compare
527e504 to
096833f
Compare
|
[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 DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
This patch left hardcoded secret name in
but it's cleaned later, when commonizing GenerateConfigs so no problem.There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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..
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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,
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
Should be part of the previous commit.
| Namespace: src.GetNamespace(), | ||
| } | ||
| err := r.List(ctx, crList, listOps) | ||
| err := r.Client.List(ctx, crList, listOps) |
|
|
||
| .PHONY: gotest-unit | ||
| gotest-unit: ## Run unit tests under test/unit/. | ||
| go test ./test/unit/... |
There was a problem hiding this comment.
adding -v would be nice to make sure all tests are being executed.
go test -v ./test/unit/...
| # 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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Added more coverage. PTAL
| ctrl "sigs.k8s.io/controller-runtime" | ||
| ) | ||
|
|
||
| func TestEnsureNetworkAttachments_noAttachments(t *testing.T) { |
There was a problem hiding this comment.
Nit: similar to ensureSecret, missing coverage for other errors than NotFound.
There was a problem hiding this comment.
Added more coverage. PTAL
| newList func() L, | ||
| getItems func(L) S, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Updated. Thanks
2aaf127 to
e832d30
Compare
…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.
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>
|
Build failed (check pipeline). Post ✔️ openstack-meta-content-provider SUCCESS in 2h 28m 20s |
|
recheck |
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