Repository navigation
OSAC-1755: Add capabilities intersection to NetworkClass reconciler #406
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -65,6 +65,8 @@ import ( | |||||||||||||||||||||||
| "github.com/osac-project/osac-operator/internal/controller" | ||||||||||||||||||||||||
| "github.com/osac-project/osac-operator/internal/migrations" | ||||||||||||||||||||||||
| "github.com/osac-project/osac-operator/pkg/aap" | ||||||||||||||||||||||||
| "github.com/osac-project/osac-operator/pkg/dispatcher" | ||||||||||||||||||||||||
| "github.com/osac-project/osac-operator/pkg/networkmanager" | ||||||||||||||||||||||||
| "github.com/osac-project/osac-operator/pkg/provisioning" | ||||||||||||||||||||||||
| // +kubebuilder:scaffold:imports | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
|
|
@@ -102,6 +104,9 @@ const ( | |||||||||||||||||||||||
| // Job history configuration | ||||||||||||||||||||||||
| envMaxJobHistory = "OSAC_MAX_JOB_HISTORY" | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // NetworkClass capabilities sync configuration | ||||||||||||||||||||||||
| envNetworkClassSyncInterval = "OSAC_NETWORK_CLASS_SYNC_INTERVAL" | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Tenant configuration | ||||||||||||||||||||||||
| envTenantNamespace = "OSAC_TENANT_NAMESPACE" | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
@@ -117,6 +122,10 @@ const ( | |||||||||||||||||||||||
| envEnableBareMetalInstanceController = "OSAC_ENABLE_BAREMETAL_INSTANCE_CONTROLLER" | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| remoteClusterName = "remote" | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // defaultNetworkClassSyncInterval is the fallback periodic resync interval for | ||||||||||||||||||||||||
| // NetworkClass capabilities when OSAC_NETWORK_CLASS_SYNC_INTERVAL is unset. | ||||||||||||||||||||||||
| defaultNetworkClassSyncInterval = 5 * time.Minute | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // controllerFlags holds the enable flags for all controllers. | ||||||||||||||||||||||||
|
|
@@ -495,6 +504,14 @@ func setupNetworkingControllers( | |||||||||||||||||||||||
| return fmt.Errorf("externalip attachment provider: %w", err) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if grpcConn != nil && networkingNamespace != "" { | ||||||||||||||||||||||||
| if err := setupNetworkClassCapabilitiesController( | ||||||||||||||||||||||||
| mgr, localMgr, grpcConn, networkingNamespace, | ||||||||||||||||||||||||
| ); err != nil { | ||||||||||||||||||||||||
| return err | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if err := setupVirtualNetworkControllers( | ||||||||||||||||||||||||
| mgr, localMgr, grpcConn, networkingNamespace, | ||||||||||||||||||||||||
| networkingProvider, statusPollInterval, maxJobHistory, targetCluster, | ||||||||||||||||||||||||
|
|
@@ -541,6 +558,33 @@ func setupNetworkingControllers( | |||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // setupNetworkClassCapabilitiesController registers the NetworkClass capabilities | ||||||||||||||||||||||||
| // reconciler (ConfigMap-triggered) and its periodic resync runnable. Both require the | ||||||||||||||||||||||||
| // gRPC connection and a manager resolver built from ConfigMap-based discovery, so this | ||||||||||||||||||||||||
| // is only called when grpcConn is set and a networking namespace is configured. | ||||||||||||||||||||||||
| func setupNetworkClassCapabilitiesController( | ||||||||||||||||||||||||
| mgr mcmanager.Manager, localMgr ctrl.Manager, grpcConn *grpc.ClientConn, networkingNamespace string, | ||||||||||||||||||||||||
| ) error { | ||||||||||||||||||||||||
| disc, err := networkmanager.NewDiscovery(localMgr.GetClient(), networkingNamespace) | ||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||
| return fmt.Errorf("network manager discovery: %w", err) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| resolver := dispatcher.NewResolver(privatev1.NewNetworkClassesClient(grpcConn), disc) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ncReconciler := controller.NewNetworkClassCapabilitiesReconciler( | ||||||||||||||||||||||||
| privatev1.NewNetworkClassesClient(grpcConn), resolver, networkingNamespace, | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
|
Comment on lines
+572
to
+576
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value Reuse a single
♻️ Proposed refactor- resolver := dispatcher.NewResolver(privatev1.NewNetworkClassesClient(grpcConn), disc)
+ ncClient := privatev1.NewNetworkClassesClient(grpcConn)
+ resolver := dispatcher.NewResolver(ncClient, disc)
ncReconciler := controller.NewNetworkClassCapabilitiesReconciler(
- privatev1.NewNetworkClassesClient(grpcConn), resolver, networkingNamespace,
+ ncClient, resolver, networkingNamespace,
)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| if err := ncReconciler.SetupWithManager(mgr); err != nil { | ||||||||||||||||||||||||
| return fmt.Errorf("networkclass capabilities controller: %w", err) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| syncInterval := helpers.GetEnvWithDefault(envNetworkClassSyncInterval, defaultNetworkClassSyncInterval) | ||||||||||||||||||||||||
| if err := localMgr.Add(controller.NewNetworkClassCapabilitiesSyncRunnable(ncReconciler, syncInterval)); err != nil { | ||||||||||||||||||||||||
| return fmt.Errorf("networkclass capabilities sync runnable: %w", err) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
+581
to
+584
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Validate that the sync interval is positive before use.
Add a positive-duration validator to the 🛡️ Proposed fix- syncInterval := helpers.GetEnvWithDefault(envNetworkClassSyncInterval, defaultNetworkClassSyncInterval)
+ syncInterval := helpers.GetEnvWithDefault(
+ envNetworkClassSyncInterval, defaultNetworkClassSyncInterval,
+ func(d time.Duration) bool { return d > 0 },
+ )📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| func setupVirtualNetworkControllers( | ||||||||||||||||||||||||
| mgr mcmanager.Manager, localMgr ctrl.Manager, grpcConn *grpc.ClientConn, | ||||||||||||||||||||||||
| networkingNamespace string, provider provisioning.ProvisioningProvider, | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| /* | ||
| Copyright 2026. | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package controller | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
|
|
||
| "google.golang.org/grpc" | ||
| corev1 "k8s.io/api/core/v1" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
|
|
||
| privatev1 "github.com/osac-project/osac-operator/internal/api/osac/private/v1" | ||
| "github.com/osac-project/osac-operator/pkg/networkmanager" | ||
| ) | ||
|
|
||
| // stubNetworkClassesClient is a minimal test double for privatev1.NetworkClassesClient | ||
| // used by controller tests exercising manager-resolution paths (e.g. the OSAC-1755 | ||
| // capabilities reconciler). Embedding the interface (nil) satisfies methods that | ||
| // aren't stubbed for a given test, which are not expected to be called. | ||
| type stubNetworkClassesClient struct { | ||
| privatev1.NetworkClassesClient | ||
| getFunc func(ctx context.Context, in *privatev1.NetworkClassesGetRequest, opts ...grpc.CallOption) (*privatev1.NetworkClassesGetResponse, error) | ||
| listFunc func(ctx context.Context, in *privatev1.NetworkClassesListRequest, opts ...grpc.CallOption) (*privatev1.NetworkClassesListResponse, error) | ||
| updateFunc func(ctx context.Context, in *privatev1.NetworkClassesUpdateRequest, opts ...grpc.CallOption) (*privatev1.NetworkClassesUpdateResponse, error) | ||
| } | ||
|
|
||
| func (s *stubNetworkClassesClient) Get( | ||
| ctx context.Context, in *privatev1.NetworkClassesGetRequest, opts ...grpc.CallOption, | ||
| ) (*privatev1.NetworkClassesGetResponse, error) { | ||
| if s.getFunc != nil { | ||
| return s.getFunc(ctx, in, opts...) | ||
| } | ||
| return s.NetworkClassesClient.Get(ctx, in, opts...) | ||
| } | ||
|
|
||
| func (s *stubNetworkClassesClient) List( | ||
| ctx context.Context, in *privatev1.NetworkClassesListRequest, opts ...grpc.CallOption, | ||
| ) (*privatev1.NetworkClassesListResponse, error) { | ||
| if s.listFunc != nil { | ||
| return s.listFunc(ctx, in, opts...) | ||
| } | ||
| return s.NetworkClassesClient.List(ctx, in, opts...) | ||
| } | ||
|
|
||
| func (s *stubNetworkClassesClient) Update( | ||
| ctx context.Context, in *privatev1.NetworkClassesUpdateRequest, opts ...grpc.CallOption, | ||
| ) (*privatev1.NetworkClassesUpdateResponse, error) { | ||
| if s.updateFunc != nil { | ||
| return s.updateFunc(ctx, in, opts...) | ||
| } | ||
| return s.NetworkClassesClient.Update(ctx, in, opts...) | ||
| } | ||
|
|
||
| // newListingNetworkClassClient returns a stub NetworkClassesClient whose List method | ||
| // returns the given NetworkClasses and whose Get method finds one by ID among them. | ||
| // Update appends to the captured updates slice so tests can assert on what was saved. | ||
| func newListingNetworkClassClient( | ||
| items []*privatev1.NetworkClass, updates *[]*privatev1.NetworkClass, | ||
| ) *stubNetworkClassesClient { | ||
| return &stubNetworkClassesClient{ | ||
| listFunc: func(_ context.Context, _ *privatev1.NetworkClassesListRequest, _ ...grpc.CallOption) (*privatev1.NetworkClassesListResponse, error) { | ||
| return &privatev1.NetworkClassesListResponse{Items: items}, nil | ||
| }, | ||
| getFunc: func(_ context.Context, in *privatev1.NetworkClassesGetRequest, _ ...grpc.CallOption) (*privatev1.NetworkClassesGetResponse, error) { | ||
| for _, item := range items { | ||
| if item.GetId() == in.GetId() { | ||
| return &privatev1.NetworkClassesGetResponse{Object: item}, nil | ||
| } | ||
| } | ||
| return nil, fmt.Errorf("network class %q not found", in.GetId()) | ||
| }, | ||
| updateFunc: func(_ context.Context, in *privatev1.NetworkClassesUpdateRequest, _ ...grpc.CallOption) (*privatev1.NetworkClassesUpdateResponse, error) { | ||
| *updates = append(*updates, in.GetObject()) | ||
| return &privatev1.NetworkClassesUpdateResponse{Object: in.GetObject()}, nil | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| // newFabricManagerConfigMap builds a fabric-manager registration ConfigMap for tests | ||
| // that exercise dispatcher-backed manager discovery. | ||
| func newFabricManagerConfigMap(name, namespace, managerName string) *corev1.ConfigMap { | ||
| return newManagerConfigMap(name, namespace, managerName, networkmanager.LabelFabricManager, "ipv4") | ||
| } | ||
|
|
||
| // newK8sManagerConfigMap builds a k8s-manager registration ConfigMap for tests that | ||
| // exercise dispatcher-backed manager discovery. | ||
| func newK8sManagerConfigMap(name, namespace, managerName, capabilities string) *corev1.ConfigMap { | ||
| return newManagerConfigMap(name, namespace, managerName, networkmanager.LabelK8sManager, capabilities) | ||
| } | ||
|
|
||
| func newManagerConfigMap(name, namespace, managerName, labelKey, capabilities string) *corev1.ConfigMap { | ||
| return &corev1.ConfigMap{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: name, | ||
| Namespace: namespace, | ||
| Labels: map[string]string{labelKey: "true"}, | ||
| }, | ||
| Data: map[string]string{ | ||
| "name": managerName, | ||
| "capabilities": capabilities, | ||
| }, | ||
| } | ||
| } |
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.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Document the namespace prerequisite too.
This entry states the sync is "Only active when the fulfillment-service gRPC connection is configured." The actual gate in
cmd/main.goalso requires a non-empty networking namespace (grpcConn != nil && networkingNamespace != ""). Mention both prerequisites so operators do not troubleshoot a silently inactive sync when only the gRPC connection is set.📝 Proposed doc fix
📝 Committable suggestion
🤖 Prompt for AI Agents