-
Notifications
You must be signed in to change notification settings - Fork 34
Add Trunk resource controller implementation #581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
winiciusallan
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, @aldokimi! I've left a few comments in the API types. Thanks for submitting this pull request, it will be very important in achieving parity with CAPO.
api/v1alpha1/trunk_type.go
Outdated
| type Subport struct { | ||
| // portRef is a reference to the ORC Port which will be used as a subport. | ||
| // +required | ||
| PortRef KubernetesNameRef `json:"portRef"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| PortRef KubernetesNameRef `json:"portRef"` | |
| PortRef KubernetesNameRef `json:"portRef,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ack.
api/v1alpha1/trunk_type.go
Outdated
| PortID string `json:"portID,omitempty"` | ||
|
|
||
| // segmentationType is the type of segmentation used. | ||
| // +kubebuilder:validation:MaxLength=1024 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this should match the same validation in spec for this field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's OK if it's higher in the Status than in the Spec (the opposite is not OK).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ack.
mandre
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect you haven't used the scaffolding tool. If it's not too much trouble, would it be possible for you to re-work your controller to use it? It ensures consistency with the other controllers and makes it much easier for reviewers to see your modifications, since there's typically a lot of boilerplate code in PRs for new controllers. See #568 (comment) for the rational. I'm currently working on updating the developers doc to make it more obvious.
I bet it was a lot of work to contribute this controller without the help of the scaffolding tool, so if that turns out to be too complicated to rework your change to make use of it, I'm ready to make an exception.
FYI, here's how I expect us to run the scaffolding tool, based on what the resource supports:
go run ./cmd/scaffold-controller \
-interactive=false \
-kind Trunk \
-gophercloud-client NewNetworkV2 \
-gophercloud-module github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/trunks \
-import-dependency Port \
-import-dependency Project \
-optional-create-dependency Project \
-required-create-dependency Port
api/v1alpha1/trunk_type.go
Outdated
| PortID string `json:"portID,omitempty"` | ||
|
|
||
| // segmentationType is the type of segmentation used. | ||
| // +kubebuilder:validation:MaxLength=1024 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's OK if it's higher in the Status than in the Spec (the opposite is not OK).
ae0d632 to
b1f5243
Compare
|
Failed to assess the semver bump. See logs for details. |
winiciusallan
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @aldokimi, I've left more comments. This kind of PR tends to be very large, so I'll need to take another look. But for now, thanks for addressing the comments. =D
cmd/resource-generator/main.go
Outdated
| }, | ||
| { | ||
| Name: "Trunk", | ||
| ExistingOSClient: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please remove this field? This uses an existing client, but we want to separate clients for each controller.
You may remove and run make generate. Maybe do you need to refactor other parts along the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ack.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I don't really understand why removing this field is necessary, could elaborate more please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. To be honest, I don't know the exactly reason. I believe @mandre can explain it better.
My guess is, if you need some extra logic for a specific client, you won't impact the others implemented in the same interface. As I said, is just I guess, so don't take it as the truth 😅.
| return nil | ||
| } | ||
|
|
||
| updateOpts.RevisionNumber = &osResource.RevisionNumber |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May the revision number be nil, or does the API always returns a valid value?
| yield(nil, err) | ||
| }, nil | ||
| } | ||
| return func(yield func(*osResourceT, error) bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same about the comment on ListForAdoption.
internal/controllers/trunk/tests/trunk-create-minimal/00-assert.yaml
Outdated
Show resolved
Hide resolved
b1f5243 to
960bd52
Compare
|
@aldokimi I may be a little busy (resting...) in the next few days, so maybe I'll take a little while to review your commits, but at the start of the year, I should take a look into it. Thanks for contributing, we really appreciate it. =D |
|
Hey @aldokimi, I had some time to take a look into your PR. It looks like you have overrided your old implementation for Trunk types with the code generated by the scaffolding tool. Are you still working on this? Also, I saw that you changed a lot of tests files from other controllers, I believe we don't want to touch another controllers on this pull request. Do you have made this changes for a specific reason? If you are still working on this, you can ignore my comment. Let me know when you believe it is ready for another review :) |
Hello @winiciusallan , I am still going to upload a last commit, the changes on the test files where because of the scaffolding tool, my last commit will be about fixing the issues regarding the scaffolding and then we will be ready for a new review! Thank you for your comment, happy holidays! |
710a752 to
545a5fa
Compare
|
Hello @winiciusallan and @mandre , happy new year! So, I am sorry for this mess, this is the biggest PR of my life xD Thanks, |
winiciusallan
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @aldokimi, thanks for taking time to work on it.
I have left a few comments, especially on the API part. This particular part is good overall. Let's see if Martin has comments to make.
I feel that it is missing some logic on the actuator file to handle import, creation and the update of the resource. I avoided adding repetitive comments inline to not make the review cluttered. Are you probably still working on it? If you need to discuss something, I'm online on kubernetes slack workspace, or on the #gophercloud channel.
Happy new year for you too =) 🎆
Makefile
Outdated
| GOLANGCI_LINT_VERSION ?= v2.7.2 | ||
| KAL_VERSION ?= v0.0.0-20250924094418-502783c08f9d | ||
| MOCKGEN_VERSION ?= v0.5.0 | ||
| MOCKGEN_VERSION ?= v0.6.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Martin could give better feedback here, but I believe we should bump mockgen version on a separate PR to better track the changes, if it is really necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, in this case we will need this separate PR to go in before this one, because Mokcing fails if the version is 0.6.0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain how mockgen fails with v0.5.0? I've tried setting it back to v0.5.0 in a checkout from your branch and didn't see any errors when running make generate-go. What am I missing?
I don't see a problem bumping a library, but ideally we would do that in a separate PR.
api/v1alpha1/trunk_types.go
Outdated
| // segmentationType is the segmentation type for the subport (e.g. vlan). | ||
| // +required | ||
| // +kubebuilder:validation:MinLength:=1 | ||
| // +kubebuilder:validation:MaxLength:=255 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's sticky the maximum length to 32 bytes as the source code defines.
If we set it to 255, the user can pass any value, so the openstack may return a fail code whe trying to get or create the resource. If we stick to the maximum length permitted by the API, we avoid making this unecessary request to the OpenStack API.
Other suggestion is use enumeration, since the only two options are inherit and vlan. We have an example here.
| // status indicates whether the trunk is currently operational. Possible values include | ||
| // `ACTIVE', `DOWN', `BUILD', `DEGRADED' or `ERROR'. Plug-ins might define additional values. | ||
| // +optional | ||
| Status *string `json:"status,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please set a maximum length here?
When you don't specify, CEL works by calculating the worst case for that validation, so we always try to set this value, even if it is an estimate. Here it is a talk that explore more about it (thanks Martin).
| // tenantID is the project owner of the trunk (alias of projectID in some deployments). | ||
| // +kubebuilder:validation:MaxLength=1024 | ||
| // +optional | ||
| TenantID string `json:"tenantID,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although tenant_id is for old Keystone APIs, I believe it won't gonna hurt to have it here.
api/v1alpha1/trunk_types.go
Outdated
|
|
||
| // tags is a list of Neutron tags to apply to the trunk. | ||
| // | ||
| // NOTE: ORC does not currently reconcile tag updates for Trunk. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you explain better why? Is it because the networking API does not support updating the tags for the Trunk port?
If so, I believe it is worth having it annotated here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So basically, I thought that it will be a different approach to update the tags (than a normal port), but I am wrong, I will add the implementation to reconcile tags!
| // TODO(scaffolding) If you need to filter resources on fields that the List() function | ||
| // of gophercloud does not support, it's possible to perform client-side filtering. | ||
| // Check osclients.ResourceFilter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you forgot to delete it? There is others "TODO" comments along this same file.
| Description: string(ptr.Deref(filter.Description, "")), | ||
| PortID: ptr.Deref(port.Status.ID, ""), | ||
| ProjectID: ptr.Deref(project.Status.ID, ""), | ||
| // TODO(scaffolding): Add more import filters |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we can add more fields as you declared on types. Status and AdminStateUp are missing. Could you add it please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add the AdminStateUp , but the Status is a read-only so I will skip it
mandre
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you also add the neutron-trunk service to our devstack config so that we can test this change in CI?
Makefile
Outdated
| GOLANGCI_LINT_VERSION ?= v2.7.2 | ||
| KAL_VERSION ?= v0.0.0-20250924094418-502783c08f9d | ||
| MOCKGEN_VERSION ?= v0.5.0 | ||
| MOCKGEN_VERSION ?= v0.6.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain how mockgen fails with v0.5.0? I've tried setting it back to v0.5.0 in a checkout from your branch and didn't see any errors when running make generate-go. What am I missing?
I don't see a problem bumping a library, but ideally we would do that in a separate PR.
| } | ||
| return portRefs | ||
| }, | ||
| finalizer, externalObjectFieldOwner, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll want to use a different finalizer here, for example orcstrings.GetFinalizerName("trunk-subport") to differentiate with the finalizer used for the parent port.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, I am not sure what problems I encountered earlier when I started the PR, but things now look okay, so I will be removing the MOCKGEN_VERSION update.
Regarding creating a new Finalizer name, this fixed the issue!
| name: trunk-dependency-no-project | ||
| spec: | ||
| cloudCredentialsRef: | ||
| cloudName: openstack |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll need elevated privilege to create a trunk within a specific project:
| cloudName: openstack | |
| cloudName: openstack-admin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure? The policy reference says that he can create the trunk if he's a project member
I can't link the rule, but it is create_trunk https://docs.openstack.org/neutron/latest/configuration/policy.html
| name: trunk-dependency | ||
| spec: | ||
| cloudCredentialsRef: | ||
| cloudName: openstack |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| cloudName: openstack | |
| cloudName: openstack-admin |
|
Failed to assess the semver bump. See logs for details. |
dacf651 to
82e2ff6
Compare
winiciusallan
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a few nit comments -- for those, I think they wouldn't block you PR, so it is up to you.
There is other comments that need more attention.
Some points on testing:
- We use the xx-assert.yaml files to validate the expected status of the resource, so having both the messages and the resource fields there helps the tests work as expected.
- We recommend to use CEL expressions when you don't know the field value upfront, so you can make elaborate validation with it if necessary.
| return actuator.osClient.ListTrunks(ctx, listOpts), true | ||
| } | ||
|
|
||
| func (actuator trunkActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we can refactor later these dependencies to use the FetchDependency helper implemented in #621
maybe in another PR, since it doesn't impact the controller functionality.
| NotTagsAny: tags.Join(filter.NotTagsAny), | ||
| } | ||
| if filter.Status != nil { | ||
| listOpts.Status = *filter.Status | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: you may can refactor this part
| NotTagsAny: tags.Join(filter.NotTagsAny), | |
| } | |
| if filter.Status != nil { | |
| listOpts.Status = *filter.Status | |
| } | |
| NotTagsAny: tags.Join(filter.NotTagsAny), | |
| Status: ptr.Deref(filter.Status, ""), | |
| } |
| } | ||
|
|
||
| // Build actual subports map: portID -> subport | ||
| actualSubports := make(map[string]trunks.Subport) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: you can set a length for the subport port here, so the runtime allocates the necessary memory upfront.
| actualSubports := make(map[string]trunks.Subport) | |
| actualSubports := make(map[string]trunks.Subport, osResource.Subports) |
| if len(subportsToAdd) != tt.expectedToAdd { | ||
| t.Errorf("Expected %d subports to add, got %d", tt.expectedToAdd, len(subportsToAdd)) | ||
| } | ||
| if len(subportsToRemove) != tt.expectedToRemove { | ||
| t.Errorf("Expected %d subports to remove, got %d", tt.expectedToRemove, len(subportsToRemove)) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For better consistency, wouldn't it be better to use some AssertEquals function to compare if subports to add/remove are as expected, instead of comparing the number of subports? What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that the changes on this file should be in the dedicated file internal/osclients/mock/trunk.go, likely a result of the resource-generator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought the same initially but then I changed my mind. The reason being that:
- all of the networking client calls are already in the same file
- more importantly, we need a reference over the network client anyway for the management of the tags, and if we can avoid to keep a reference to 2 clients in the trunk controller, that's better.
I suggest deleting the internal/osclients/trunk.go and using the NetworkClient instead of a dedicated TrunkClient.
| name: trunk-dependency-no-project | ||
| spec: | ||
| cloudCredentialsRef: | ||
| cloudName: openstack |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure? The policy reference says that he can create the trunk if he's a project member
I can't link the rule, but it is create_trunk https://docs.openstack.org/neutron/latest/configuration/policy.html
| cloudName: openstack | ||
| secretName: openstack-clouds | ||
| managementPolicy: managed | ||
| resource: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We usually add all the available fields in the spec on create-full scenario, so I think it would be good to have adminStateUp and []tags here
| - celExpr: "trunk.status.resource.subports.size() == 2" | ||
| - celExpr: "trunk.status.resource.subports.exists(s, s.portID == subport1.status.id && s.segmentationID == 100 && s.segmentationType == 'vlan')" | ||
| - celExpr: "trunk.status.resource.subports.exists(s, s.portID == subport2.status.id && s.segmentationID == 200 && s.segmentationType == 'vlan')" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using CEL expressions to validate the resource status, you can fill in the trunk status with what you are expecting, and KUTTL will match the declared status with the actual status.
| - type: Available | ||
| message: Waiting for Port/trunk-import to be created | ||
| status: "False" | ||
| reason: Progressing | ||
| - type: Progressing | ||
| message: Waiting for Port/trunk-import to be created | ||
| status: "True" | ||
| reason: Progressing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is better if we don't rely on a transitional state here, we are already testing it in trunk-import-dependency, so I believe you can create the dependency on 00-import-resource.yaml and use it.
For better context: #535 (comment)
| assertAll: | ||
| - celExpr: "trunk.status.id != ''" | ||
| - celExpr: "trunk.status.resource.portID == port.status.id" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| assertAll: | |
| - celExpr: "trunk.status.id != ''" | |
| - celExpr: "trunk.status.resource.portID == port.status.id" | |
| assertAll: | |
| - celExpr: "trunk.status.id != ''" | |
| - celExpr: "trunk.status.resource.portID == port.status.id" | |
| - celExpr: "!has(trunk.status.resource.description)" |
Summary
Adds complete Trunk resource controller implementation with subport implementation (which is necessary for the Trunk type), dependency handling, and comprehensive test coverage.
Key Features
Testing
E2E Testing
-All resources are created in the K8s side
-All resources are created in the Openstack side
-All CRUD functionalities are passing the tests
Checklist