From a7ff5a0a3b8329002de7b62fe992b619ee8e10be Mon Sep 17 00:00:00 2001 From: Yury Kulazhenkov Date: Fri, 1 Mar 2024 16:05:19 +0200 Subject: [PATCH 1/3] Add OVSNetwork CRD and dummy controller for it Signed-off-by: Yury Kulazhenkov --- Makefile | 1 + PROJECT | 9 ++ api/v1/ovsnetwork_types.go | 91 ++++++++++++ api/v1/zz_generated.deepcopy.go | 130 ++++++++++++++++++ ...sriovnetwork.openshift.io_ovsnetworks.yaml | 99 +++++++++++++ config/crd/kustomization.yaml | 3 + .../patches/cainjection_in_ovsnetworks.yaml | 7 + .../crd/patches/webhook_in_ovsnetworks.yaml | 16 +++ config/rbac/ovsnetwork_editor_role.yaml | 26 ++++ config/rbac/ovsnetwork_viewer_role.yaml | 20 +++ .../samples/sriovnetwork_v1_ovsnetwork.yaml | 7 + controllers/ovsnetwork_controller.go | 62 +++++++++ ...sriovnetwork.openshift.io_ovsnetworks.yaml | 99 +++++++++++++ main.go | 7 + 14 files changed, 577 insertions(+) create mode 100644 api/v1/ovsnetwork_types.go create mode 100644 config/crd/bases/sriovnetwork.openshift.io_ovsnetworks.yaml create mode 100644 config/crd/patches/cainjection_in_ovsnetworks.yaml create mode 100644 config/crd/patches/webhook_in_ovsnetworks.yaml create mode 100644 config/rbac/ovsnetwork_editor_role.yaml create mode 100644 config/rbac/ovsnetwork_viewer_role.yaml create mode 100644 config/samples/sriovnetwork_v1_ovsnetwork.yaml create mode 100644 controllers/ovsnetwork_controller.go create mode 100644 deployment/sriov-network-operator/crds/sriovnetwork.openshift.io_ovsnetworks.yaml diff --git a/Makefile b/Makefile index f2c2b3b7b..e6fa128ae 100644 --- a/Makefile +++ b/Makefile @@ -117,6 +117,7 @@ sync-manifests-%: manifests sed '2{/---/d}' $(CRD_BASES)/sriovnetwork.openshift.io_sriovnetworknodestates.yaml | awk 'NF' > manifests/$*/sriov-network-operator-sriovnetworknodestate.crd.yaml sed '2{/---/d}' $(CRD_BASES)/sriovnetwork.openshift.io_sriovoperatorconfigs.yaml | awk 'NF' > manifests/$*/sriov-network-operator-sriovoperatorconfig.crd.yaml sed '2{/---/d}' $(CRD_BASES)/sriovnetwork.openshift.io_sriovnetworks.yaml | awk 'NF' > manifests/$*/sriov-network-operator-sriovnetwork.crd.yaml + sed '2{/---/d}' $(CRD_BASES)/sriovnetwork.openshift.io_ovsnetworks.yaml | awk 'NF' > manifests/$*/sriov-network-operator-ovsnetwork.yaml @echo "" @echo "*************************************************************************************************************************************************" @echo "* Please manually update the sriov-network-operator.v4.7.0.clusterserviceversion.yaml and image-references files in the manifests/$* directory *" diff --git a/PROJECT b/PROJECT index 2eec23eaf..30fa5d903 100644 --- a/PROJECT +++ b/PROJECT @@ -49,6 +49,15 @@ resources: kind: SriovOperatorConfig path: github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1 version: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: openshift.io + group: sriovnetwork + kind: OVSNetwork + path: github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1 + version: v1 version: "3" plugins: manifests.sdk.operatorframework.io/v2: {} diff --git a/api/v1/ovsnetwork_types.go b/api/v1/ovsnetwork_types.go new file mode 100644 index 000000000..05b69bf7f --- /dev/null +++ b/api/v1/ovsnetwork_types.go @@ -0,0 +1,91 @@ +/* +Copyright 2024. + +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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// OVSNetworkSpec defines the desired state of OVSNetwork +type OVSNetworkSpec struct { + // Namespace of the NetworkAttachmentDefinition custom resource + NetworkNamespace string `json:"networkNamespace,omitempty"` + // OVS Network device plugin endpoint resource name + ResourceName string `json:"resourceName"` + // Capabilities to be configured for this network. + // Capabilities supported: (mac|ips), e.g. '{"mac": true}' + Capabilities string `json:"capabilities,omitempty"` + // IPAM configuration to be used for this network. + IPAM string `json:"ipam,omitempty"` + // MetaPluginsConfig configuration to be used in order to chain metaplugins + MetaPluginsConfig string `json:"metaPlugins,omitempty"` + // name of the OVS bridge, if not set OVS will automatically select bridge + // based on VF PCI address + Bridge string `json:"bridge,omitempty"` + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=4095 + // Vlan to assign for the OVS port + Vlan uint `json:"vlan,omitempty"` + // Mtu for the OVS port + MTU uint `json:"mtu,omitempty"` + // Trunk configuration for the OVS port + Trunk []*TrunkConfig `json:"trunk,omitempty"` + // The type of interface on ovs. + InterfaceType string `json:"interfaceType,omitempty"` +} + +// TrunkConfig contains configuration for bridge trunk +type TrunkConfig struct { + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=4095 + MinID *uint `json:"minID,omitempty"` + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=4095 + MaxID *uint `json:"maxID,omitempty"` + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=4095 + ID *uint `json:"id,omitempty"` +} + +// OVSNetworkStatus defines the observed state of OVSNetwork +type OVSNetworkStatus struct { +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// OVSNetwork is the Schema for the ovsnetworks API +type OVSNetwork struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec OVSNetworkSpec `json:"spec,omitempty"` + Status OVSNetworkStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// OVSNetworkList contains a list of OVSNetwork +type OVSNetworkList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []OVSNetwork `json:"items"` +} + +func init() { + SchemeBuilder.Register(&OVSNetwork{}, &OVSNetworkList{}) +} diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 5e13174ee..22d6055f5 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -130,6 +130,106 @@ func (in Interfaces) DeepCopy() Interfaces { return *out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OVSNetwork) DeepCopyInto(out *OVSNetwork) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OVSNetwork. +func (in *OVSNetwork) DeepCopy() *OVSNetwork { + if in == nil { + return nil + } + out := new(OVSNetwork) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OVSNetwork) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OVSNetworkList) DeepCopyInto(out *OVSNetworkList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]OVSNetwork, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OVSNetworkList. +func (in *OVSNetworkList) DeepCopy() *OVSNetworkList { + if in == nil { + return nil + } + out := new(OVSNetworkList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OVSNetworkList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OVSNetworkSpec) DeepCopyInto(out *OVSNetworkSpec) { + *out = *in + if in.Trunk != nil { + in, out := &in.Trunk, &out.Trunk + *out = make([]*TrunkConfig, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(TrunkConfig) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OVSNetworkSpec. +func (in *OVSNetworkSpec) DeepCopy() *OVSNetworkSpec { + if in == nil { + return nil + } + out := new(OVSNetworkSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OVSNetworkStatus) DeepCopyInto(out *OVSNetworkStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OVSNetworkStatus. +func (in *OVSNetworkStatus) DeepCopy() *OVSNetworkStatus { + if in == nil { + return nil + } + out := new(OVSNetworkStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OvsHardwareOffloadConfig) DeepCopyInto(out *OvsHardwareOffloadConfig) { *out = *in @@ -785,6 +885,36 @@ func (in *SriovOperatorConfigStatus) DeepCopy() *SriovOperatorConfigStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrunkConfig) DeepCopyInto(out *TrunkConfig) { + *out = *in + if in.MinID != nil { + in, out := &in.MinID, &out.MinID + *out = new(uint) + **out = **in + } + if in.MaxID != nil { + in, out := &in.MaxID, &out.MaxID + *out = new(uint) + **out = **in + } + if in.ID != nil { + in, out := &in.ID, &out.ID + *out = new(uint) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrunkConfig. +func (in *TrunkConfig) DeepCopy() *TrunkConfig { + if in == nil { + return nil + } + out := new(TrunkConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VfGroup) DeepCopyInto(out *VfGroup) { *out = *in diff --git a/config/crd/bases/sriovnetwork.openshift.io_ovsnetworks.yaml b/config/crd/bases/sriovnetwork.openshift.io_ovsnetworks.yaml new file mode 100644 index 000000000..cbeea7c83 --- /dev/null +++ b/config/crd/bases/sriovnetwork.openshift.io_ovsnetworks.yaml @@ -0,0 +1,99 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.0 + creationTimestamp: null + name: ovsnetworks.sriovnetwork.openshift.io +spec: + group: sriovnetwork.openshift.io + names: + kind: OVSNetwork + listKind: OVSNetworkList + plural: ovsnetworks + singular: ovsnetwork + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: OVSNetwork is the Schema for the ovsnetworks API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: OVSNetworkSpec defines the desired state of OVSNetwork + properties: + bridge: + description: name of the OVS bridge, if not set OVS will automatically + select bridge based on VF PCI address + type: string + capabilities: + description: 'Capabilities to be configured for this network. Capabilities + supported: (mac|ips), e.g. ''{"mac": true}''' + type: string + interfaceType: + description: The type of interface on ovs. + type: string + ipam: + description: IPAM configuration to be used for this network. + type: string + metaPlugins: + description: MetaPluginsConfig configuration to be used in order to + chain metaplugins + type: string + mtu: + description: Mtu for the OVS port + type: integer + networkNamespace: + description: Namespace of the NetworkAttachmentDefinition custom resource + type: string + resourceName: + description: OVS Network device plugin endpoint resource name + type: string + trunk: + description: Trunk configuration for the OVS port + items: + description: TrunkConfig contains configuration for bridge trunk + properties: + id: + maximum: 4095 + minimum: 0 + type: integer + maxID: + maximum: 4095 + minimum: 0 + type: integer + minID: + maximum: 4095 + minimum: 0 + type: integer + type: object + type: array + vlan: + description: Vlan to assign for the OVS port + maximum: 4095 + minimum: 0 + type: integer + required: + - resourceName + type: object + status: + description: OVSNetworkStatus defines the observed state of OVSNetwork + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 6e4c3d59a..010f1b446 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -8,6 +8,7 @@ resources: - bases/sriovnetwork.openshift.io_sriovnetworknodepolicies.yaml - bases/sriovnetwork.openshift.io_sriovoperatorconfigs.yaml - bases/sriovnetwork.openshift.io_sriovnetworkpoolconfigs.yaml +- bases/sriovnetwork.openshift.io_ovsnetworks.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -19,6 +20,7 @@ patchesStrategicMerge: #- patches/webhook_in_sriovnetworknodepolicies.yaml #- patches/webhook_in_sriovoperatorconfigs.yaml #- patches/webhook_in_sriovnetworkpoolconfigs.yaml +#- patches/webhook_in_ovsnetworks.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. @@ -29,6 +31,7 @@ patchesStrategicMerge: #- patches/cainjection_in_sriovnetworknodepolicies.yaml #- patches/cainjection_in_sriovoperatorconfigs.yaml #- patches/cainjection_in_sriovnetworkpoolconfigs.yaml +#- patches/cainjection_in_ovsnetworks.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_ovsnetworks.yaml b/config/crd/patches/cainjection_in_ovsnetworks.yaml new file mode 100644 index 000000000..2386c4543 --- /dev/null +++ b/config/crd/patches/cainjection_in_ovsnetworks.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: ovsnetworks.sriovnetwork.openshift.io diff --git a/config/crd/patches/webhook_in_ovsnetworks.yaml b/config/crd/patches/webhook_in_ovsnetworks.yaml new file mode 100644 index 000000000..c38d04145 --- /dev/null +++ b/config/crd/patches/webhook_in_ovsnetworks.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: ovsnetworks.sriovnetwork.openshift.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/rbac/ovsnetwork_editor_role.yaml b/config/rbac/ovsnetwork_editor_role.yaml new file mode 100644 index 000000000..8e5ee66ab --- /dev/null +++ b/config/rbac/ovsnetwork_editor_role.yaml @@ -0,0 +1,26 @@ +# permissions for end users to edit ovsnetworks. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata:plugins: + manifests.sdk.operatorframework.io/v2: {} + scorecard.sdk.operatorframework.io/v2: {} + name: ovsnetwork-editor-role +rules: +- apiGroups: + - sriovnetwork.openshift.io + resources: + - ovsnetworks + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - sriovnetwork.openshift.io + resources: + - ovsnetworks/status + verbs: + - get diff --git a/config/rbac/ovsnetwork_viewer_role.yaml b/config/rbac/ovsnetwork_viewer_role.yaml new file mode 100644 index 000000000..b5b0cb981 --- /dev/null +++ b/config/rbac/ovsnetwork_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view ovsnetworks. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: ovsnetwork-viewer-role +rules: +- apiGroups: + - sriovnetwork.openshift.io + resources: + - ovsnetworks + verbs: + - get + - list + - watch +- apiGroups: + - sriovnetwork.openshift.io + resources: + - ovsnetworks/status + verbs: + - get diff --git a/config/samples/sriovnetwork_v1_ovsnetwork.yaml b/config/samples/sriovnetwork_v1_ovsnetwork.yaml new file mode 100644 index 000000000..efe4f57d2 --- /dev/null +++ b/config/samples/sriovnetwork_v1_ovsnetwork.yaml @@ -0,0 +1,7 @@ +apiVersion: sriovnetwork.openshift.io/v1 +kind: OVSNetwork +metadata: + name: ovsnetwork-sample +spec: + # Add fields here + foo: bar diff --git a/controllers/ovsnetwork_controller.go b/controllers/ovsnetwork_controller.go new file mode 100644 index 000000000..79235fedd --- /dev/null +++ b/controllers/ovsnetwork_controller.go @@ -0,0 +1,62 @@ +/* +Copyright 2024. + +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 controllers + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + sriovnetworkv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1" +) + +// OVSNetworkReconciler reconciles a OVSNetwork object +type OVSNetworkReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=sriovnetwork.openshift.io,resources=ovsnetworks,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=sriovnetwork.openshift.io,resources=ovsnetworks/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=sriovnetwork.openshift.io,resources=ovsnetworks/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the OVSNetwork object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.1/pkg/reconcile +func (r *OVSNetworkReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *OVSNetworkReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&sriovnetworkv1.OVSNetwork{}). + Complete(r) +} diff --git a/deployment/sriov-network-operator/crds/sriovnetwork.openshift.io_ovsnetworks.yaml b/deployment/sriov-network-operator/crds/sriovnetwork.openshift.io_ovsnetworks.yaml new file mode 100644 index 000000000..cbeea7c83 --- /dev/null +++ b/deployment/sriov-network-operator/crds/sriovnetwork.openshift.io_ovsnetworks.yaml @@ -0,0 +1,99 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.0 + creationTimestamp: null + name: ovsnetworks.sriovnetwork.openshift.io +spec: + group: sriovnetwork.openshift.io + names: + kind: OVSNetwork + listKind: OVSNetworkList + plural: ovsnetworks + singular: ovsnetwork + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: OVSNetwork is the Schema for the ovsnetworks API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: OVSNetworkSpec defines the desired state of OVSNetwork + properties: + bridge: + description: name of the OVS bridge, if not set OVS will automatically + select bridge based on VF PCI address + type: string + capabilities: + description: 'Capabilities to be configured for this network. Capabilities + supported: (mac|ips), e.g. ''{"mac": true}''' + type: string + interfaceType: + description: The type of interface on ovs. + type: string + ipam: + description: IPAM configuration to be used for this network. + type: string + metaPlugins: + description: MetaPluginsConfig configuration to be used in order to + chain metaplugins + type: string + mtu: + description: Mtu for the OVS port + type: integer + networkNamespace: + description: Namespace of the NetworkAttachmentDefinition custom resource + type: string + resourceName: + description: OVS Network device plugin endpoint resource name + type: string + trunk: + description: Trunk configuration for the OVS port + items: + description: TrunkConfig contains configuration for bridge trunk + properties: + id: + maximum: 4095 + minimum: 0 + type: integer + maxID: + maximum: 4095 + minimum: 0 + type: integer + minID: + maximum: 4095 + minimum: 0 + type: integer + type: object + type: array + vlan: + description: Vlan to assign for the OVS port + maximum: 4095 + minimum: 0 + type: integer + required: + - resourceName + type: object + status: + description: OVSNetworkStatus defines the observed state of OVSNetwork + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/main.go b/main.go index ba3a41c68..b60acdd59 100644 --- a/main.go +++ b/main.go @@ -219,6 +219,13 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "SriovNetworkPoolConfig") os.Exit(1) } + if err = (&controllers.OVSNetworkReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "OVSNetwork") + os.Exit(1) + } // we need a client that doesn't use the local cache for the objects drainKClient, err := client.New(restConfig, client.Options{ From f742a54f08bb20fcd1b0d7c8700cfcfb5ba18690 Mon Sep 17 00:00:00 2001 From: Yury Kulazhenkov Date: Mon, 4 Mar 2024 16:24:04 +0200 Subject: [PATCH 2/3] Add implementation for OVSNetwork controller Signed-off-by: Yury Kulazhenkov --- README.md | 38 ++++ api/v1/helper.go | 67 ++++++- api/v1/helper_test.go | 93 +++++++++ .../testdata/TestOVSRendering/chained.golden | 14 ++ .../TestOVSRendering/complexconf.golden | 14 ++ .../TestOVSRendering/simpleovs.golden | 14 ++ .../cni-config/ovs/ovs-cni-config.yaml | 43 ++++ .../{ => sriov}/sriov-cni-config.yaml | 0 controllers/ovsnetwork_controller.go | 36 ++-- controllers/ovsnetwork_controller_test.go | 189 ++++++++++++++++++ controllers/suite_test.go | 4 + main.go | 23 ++- 12 files changed, 509 insertions(+), 26 deletions(-) create mode 100644 api/v1/testdata/TestOVSRendering/chained.golden create mode 100644 api/v1/testdata/TestOVSRendering/complexconf.golden create mode 100644 api/v1/testdata/TestOVSRendering/simpleovs.golden create mode 100644 bindata/manifests/cni-config/ovs/ovs-cni-config.yaml rename bindata/manifests/cni-config/{ => sriov}/sriov-cni-config.yaml (100%) create mode 100644 controllers/ovsnetwork_controller_test.go diff --git a/README.md b/README.md index 1cb389859..ba36699d0 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ The SR-IOV network operator introduces following new CRDs: - SriovNetwork +- OVSNetwork + - SriovNetworkNodeState - SriovNetworkNodePolicy @@ -101,6 +103,42 @@ spec: } ``` +### OVSNetwork + +A custom resource of OVSNetwork could represent the a layer-2 broadcast domain attached to Open vSwitch that works in HW-offloading mode. +It is primarily used to generate a NetworkAttachmentDefinition CR with an OVS CNI plugin configuration. + +The OVSNetwork CR also contains the `resourceName` which is aligned with the `resourceName` of SR-IOV device plugin. One OVSNetwork obj maps to one `resourceName`, but one `resourceName` can be shared by different OVSNetwork CRs. + +It is expected that `resourceName` contains name of the resource pool which holds Virtual Functions of a NIC in the switchdev mode. +A Physical function of the NIC should be attached to an OVS bridge before any workload which uses OVSNetwork starts. + +Example: + +```yaml +apiVersion: sriovnetwork.openshift.io/v1 +kind: OVSNetwork +metadata: + name: example-network + namespace: example-namespace +spec: + ipam: | + { + "type": "host-local", + "subnet": "10.56.217.0/24", + "rangeStart": "10.56.217.171", + "rangeEnd": "10.56.217.181", + "routes": [{ + "dst": "0.0.0.0/0" + }], + "gateway": "10.56.217.1" + } + vlan: 100 + bridge: my-bridge + mtu: 2500 + resourceName: switchdevnics +``` + ### SriovNetworkNodeState The custom resource to represent the SR-IOV interface states of each host, which should only be managed by the operator itself. diff --git a/api/v1/helper.go b/api/v1/helper.go index 8b1cf9c96..686e7a169 100644 --- a/api/v1/helper.go +++ b/api/v1/helper.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "os" + "path/filepath" "regexp" "sort" "strconv" @@ -660,7 +661,7 @@ func (cr *SriovIBNetwork) RenderNetAttDef() (*uns.Unstructured, error) { data.Data["LogLevelConfigured"] = false data.Data["LogFileConfigured"] = false - objs, err := render.RenderDir(ManifestsPath, &data) + objs, err := render.RenderDir(filepath.Join(ManifestsPath, "sriov"), &data) if err != nil { return nil, err } @@ -778,7 +779,7 @@ func (cr *SriovNetwork) RenderNetAttDef() (*uns.Unstructured, error) { data.Data["LogFileConfigured"] = (cr.Spec.LogFile != "") data.Data["LogFile"] = cr.Spec.LogFile - objs, err := render.RenderDir(ManifestsPath, &data) + objs, err := render.RenderDir(filepath.Join(ManifestsPath, "sriov"), &data) if err != nil { return nil, err } @@ -794,6 +795,68 @@ func (cr *SriovNetwork) NetworkNamespace() string { return cr.Spec.NetworkNamespace } +// RenderNetAttDef renders a net-att-def for sriov CNI +func (cr *OVSNetwork) RenderNetAttDef() (*uns.Unstructured, error) { + logger := log.WithName("RenderNetAttDef") + logger.Info("Start to render OVS CNI NetworkAttachmentDefinition") + + // render RawCNIConfig manifests + data := render.MakeRenderData() + data.Data["CniType"] = "ovs" + data.Data["NetworkName"] = cr.Name + if cr.Spec.NetworkNamespace == "" { + data.Data["NetworkNamespace"] = cr.Namespace + } else { + data.Data["NetworkNamespace"] = cr.Spec.NetworkNamespace + } + data.Data["CniResourceName"] = os.Getenv("RESOURCE_PREFIX") + "/" + cr.Spec.ResourceName + + if cr.Spec.Capabilities == "" { + data.Data["CapabilitiesConfigured"] = false + } else { + data.Data["CapabilitiesConfigured"] = true + data.Data["CniCapabilities"] = cr.Spec.Capabilities + } + + data.Data["Bridge"] = cr.Spec.Bridge + data.Data["VlanTag"] = cr.Spec.Vlan + data.Data["MTU"] = cr.Spec.MTU + if len(cr.Spec.Trunk) > 0 { + trunkConfRaw, _ := json.Marshal(cr.Spec.Trunk) + data.Data["Trunk"] = string(trunkConfRaw) + } else { + data.Data["Trunk"] = "" + } + data.Data["InterfaceType"] = cr.Spec.InterfaceType + + if cr.Spec.IPAM != "" { + data.Data["CniIpam"] = SriovCniIpam + ":" + strings.Join(strings.Fields(cr.Spec.IPAM), "") + } else { + data.Data["CniIpam"] = SriovCniIpamEmpty + } + + data.Data["MetaPluginsConfigured"] = false + if cr.Spec.MetaPluginsConfig != "" { + data.Data["MetaPluginsConfigured"] = true + data.Data["MetaPlugins"] = cr.Spec.MetaPluginsConfig + } + + objs, err := render.RenderDir(filepath.Join(ManifestsPath, "ovs"), &data) + if err != nil { + return nil, err + } + for _, obj := range objs { + raw, _ := json.Marshal(obj) + logger.Info("render NetworkAttachmentDefinition output", "raw", string(raw)) + } + return objs[0], nil +} + +// NetworkNamespace returns target network namespace for the network +func (cr *OVSNetwork) NetworkNamespace() string { + return cr.Spec.NetworkNamespace +} + // NetFilterMatch -- parse netFilter and check for a match func NetFilterMatch(netFilter string, netValue string) (isMatch bool) { logger := log.WithName("NetFilterMatch") diff --git a/api/v1/helper_test.go b/api/v1/helper_test.go index 860827f63..cae4e2a00 100644 --- a/api/v1/helper_test.go +++ b/api/v1/helper_test.go @@ -249,6 +249,99 @@ func TestIBRendering(t *testing.T) { } } +func TestOVSRendering(t *testing.T) { + testtable := []struct { + tname string + network v1.OVSNetwork + }{ + { + tname: "simpleovs", + network: v1.OVSNetwork{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1.OVSNetworkSpec{ + NetworkNamespace: "testnamespace", + ResourceName: "testresource", + }, + }, + }, + { + tname: "chained", + network: v1.OVSNetwork{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1.OVSNetworkSpec{ + NetworkNamespace: "testnamespace", + ResourceName: "testresource", + MTU: 1500, + MetaPluginsConfig: ` + { + "type": "vrf", + "vrfname": "blue" + } + `, + }, + }, + }, + { + tname: "complexconf", + network: v1.OVSNetwork{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1.OVSNetworkSpec{ + NetworkNamespace: "testnamespace", + ResourceName: "testresource", + Capabilities: `{"foo": "bar"}`, + Bridge: "test", + Vlan: 100, + MTU: 1500, + Trunk: []*v1.TrunkConfig{ + { + ID: func(i uint) *uint { return &i }(120)}, + { + MinID: func(i uint) *uint { return &i }(500), + MaxID: func(i uint) *uint { return &i }(550)}, + }, + InterfaceType: "netdev", + IPAM: `{"type": "foo"}`, + }, + }, + }, + } + for _, tc := range testtable { + t.Run(tc.tname, func(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + rendered, err := tc.network.RenderNetAttDef() + if err != nil { + t.Fatal("failed rendering network attachment definition", err) + } + encoder := json.NewEncoder(w) + encoder.SetIndent("", " ") + encoder.Encode(rendered) + w.Flush() + gp := filepath.Join("testdata", filepath.FromSlash(t.Name())+".golden") + if *update { + t.Log("update golden file") + if err := os.WriteFile(gp, b.Bytes(), 0644); err != nil { + t.Fatalf("failed to update golden file: %s", err) + } + } + g, err := os.ReadFile(gp) + if err != nil { + t.Fatalf("failed reading .golden: %s", err) + } + t.Log(b.String()) + if !bytes.Equal(b.Bytes(), g) { + t.Errorf("bytes do not match .golden file") + } + }) + } +} + func TestSriovNetworkNodePolicyApply(t *testing.T) { testtable := []struct { tname string diff --git a/api/v1/testdata/TestOVSRendering/chained.golden b/api/v1/testdata/TestOVSRendering/chained.golden new file mode 100644 index 000000000..3589e3daf --- /dev/null +++ b/api/v1/testdata/TestOVSRendering/chained.golden @@ -0,0 +1,14 @@ +{ + "apiVersion": "k8s.cni.cncf.io/v1", + "kind": "NetworkAttachmentDefinition", + "metadata": { + "annotations": { + "k8s.v1.cni.cncf.io/resourceName": "/testresource" + }, + "name": "test", + "namespace": "testnamespace" + }, + "spec": { + "config": "{ \"cniVersion\":\"0.3.1\", \"name\":\"test\",\"plugins\": [ {\"type\":\"ovs\",\"mtu\":1500,\"ipam\":{} },\n{ \"type\": \"vrf\", \"vrfname\": \"blue\" }\n] }" + } +} diff --git a/api/v1/testdata/TestOVSRendering/complexconf.golden b/api/v1/testdata/TestOVSRendering/complexconf.golden new file mode 100644 index 000000000..9806ba841 --- /dev/null +++ b/api/v1/testdata/TestOVSRendering/complexconf.golden @@ -0,0 +1,14 @@ +{ + "apiVersion": "k8s.cni.cncf.io/v1", + "kind": "NetworkAttachmentDefinition", + "metadata": { + "annotations": { + "k8s.v1.cni.cncf.io/resourceName": "/testresource" + }, + "name": "test", + "namespace": "testnamespace" + }, + "spec": { + "config": "{ \"cniVersion\":\"0.3.1\", \"name\":\"test\",\"type\":\"ovs\",\"capabilities\":{\"foo\": \"bar\"},\"bridge\":\"test\",\"vlan\":100,\"mtu\":1500,\"trunk\":[{\"id\":120},{\"minID\":500,\"maxID\":550}],\"interface_type\":\"netdev\",\"ipam\":{\"type\":\"foo\"} }" + } +} diff --git a/api/v1/testdata/TestOVSRendering/simpleovs.golden b/api/v1/testdata/TestOVSRendering/simpleovs.golden new file mode 100644 index 000000000..1f286da7c --- /dev/null +++ b/api/v1/testdata/TestOVSRendering/simpleovs.golden @@ -0,0 +1,14 @@ +{ + "apiVersion": "k8s.cni.cncf.io/v1", + "kind": "NetworkAttachmentDefinition", + "metadata": { + "annotations": { + "k8s.v1.cni.cncf.io/resourceName": "/testresource" + }, + "name": "test", + "namespace": "testnamespace" + }, + "spec": { + "config": "{ \"cniVersion\":\"0.3.1\", \"name\":\"test\",\"type\":\"ovs\",\"ipam\":{} }" + } +} diff --git a/bindata/manifests/cni-config/ovs/ovs-cni-config.yaml b/bindata/manifests/cni-config/ovs/ovs-cni-config.yaml new file mode 100644 index 000000000..30a83b7f3 --- /dev/null +++ b/bindata/manifests/cni-config/ovs/ovs-cni-config.yaml @@ -0,0 +1,43 @@ +apiVersion: "k8s.cni.cncf.io/v1" +kind: NetworkAttachmentDefinition +metadata: + name: {{.NetworkName}} + namespace: {{.NetworkNamespace}} + annotations: + k8s.v1.cni.cncf.io/resourceName: {{.CniResourceName}} +spec: + config: '{ + "cniVersion":"0.3.1", + "name":"{{.NetworkName}}", +{{- if .MetaPluginsConfigured -}} + "plugins": [ + { +{{- end -}} + "type":"{{.CniType}}", +{{- if .CapabilitiesConfigured -}} + "capabilities":{{.CniCapabilities}}, +{{- end -}} +{{- if .Bridge -}} + "bridge":"{{.Bridge}}", +{{- end -}} +{{- if .VlanTag -}} + "vlan":{{.VlanTag}}, +{{- end -}} +{{- if .MTU -}} + "mtu":{{.MTU}}, +{{- end -}} +{{- if .Trunk -}} + "trunk":{{.Trunk}}, +{{- end -}} +{{- if .InterfaceType -}} + "interface_type":"{{.InterfaceType}}", +{{- end -}} + {{.CniIpam}} +} +{{- if .MetaPluginsConfigured -}} + , + {{.MetaPlugins}} + ] +} +{{- end -}} +' diff --git a/bindata/manifests/cni-config/sriov-cni-config.yaml b/bindata/manifests/cni-config/sriov/sriov-cni-config.yaml similarity index 100% rename from bindata/manifests/cni-config/sriov-cni-config.yaml rename to bindata/manifests/cni-config/sriov/sriov-cni-config.yaml diff --git a/controllers/ovsnetwork_controller.go b/controllers/ovsnetwork_controller.go index 79235fedd..2f2eaee1e 100644 --- a/controllers/ovsnetwork_controller.go +++ b/controllers/ovsnetwork_controller.go @@ -22,7 +22,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" sriovnetworkv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1" ) @@ -30,33 +29,36 @@ import ( // OVSNetworkReconciler reconciles a OVSNetwork object type OVSNetworkReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + genericReconciler *genericNetworkReconciler } //+kubebuilder:rbac:groups=sriovnetwork.openshift.io,resources=ovsnetworks,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=sriovnetwork.openshift.io,resources=ovsnetworks/status,verbs=get;update;patch //+kubebuilder:rbac:groups=sriovnetwork.openshift.io,resources=ovsnetworks/finalizers,verbs=update -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the OVSNetwork object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.1/pkg/reconcile +// Reconcile loop for OVSNetwork CRs func (r *OVSNetworkReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) + return r.genericReconciler.Reconcile(ctx, req) +} + +// return name of the controller +func (r *OVSNetworkReconciler) Name() string { + return "OVSNetwork" +} - // TODO(user): your logic here +// return empty instance of the OVSNetwork CR +func (r *OVSNetworkReconciler) GetObject() networkCRInstance { + return &sriovnetworkv1.OVSNetwork{} +} - return ctrl.Result{}, nil +// return empty list of the OVSNetwork CRs +func (r *OVSNetworkReconciler) GetObjectList() client.ObjectList { + return &sriovnetworkv1.OVSNetworkList{} } // SetupWithManager sets up the controller with the Manager. func (r *OVSNetworkReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&sriovnetworkv1.OVSNetwork{}). - Complete(r) + r.genericReconciler = newGenericNetworkReconciler(r.Client, r.Scheme, r) + return r.genericReconciler.SetupWithManager(mgr) } diff --git a/controllers/ovsnetwork_controller_test.go b/controllers/ovsnetwork_controller_test.go new file mode 100644 index 000000000..ef464e5e9 --- /dev/null +++ b/controllers/ovsnetwork_controller_test.go @@ -0,0 +1,189 @@ +package controllers + +import ( + "context" + "sync" + "time" + + netattdefv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + v1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1" + "github.com/k8snetworkplumbingwg/sriov-network-operator/test/util" +) + +func getOvsNetworkCR() *v1.OVSNetwork { + return &v1.OVSNetwork{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: testNamespace, + }, + Spec: v1.OVSNetworkSpec{ + ResourceName: "test", + }, + } +} + +func removeOVSNetwork(ctx context.Context, cr *v1.OVSNetwork) { + err := k8sClient.Delete(ctx, cr) + if err != nil { + ExpectWithOffset(1, errors.IsNotFound(err)).To(BeTrue()) + } + EventuallyWithOffset(1, func(g Gomega) { + g.Expect(errors.IsNotFound( + k8sClient.Get(ctx, types.NamespacedName{ + Namespace: cr.Namespace, + Name: cr.Name}, &v1.OVSNetwork{}))).To(BeTrue()) + }, util.APITimeout, util.RetryInterval).Should(Succeed()) +} + +var _ = Describe("OVSNetwork Controller", Ordered, func() { + var cancel context.CancelFunc + var ctx context.Context + + BeforeAll(func() { + By("Setup controller manager") + k8sManager, err := setupK8sManagerForTest() + Expect(err).NotTo(HaveOccurred()) + + err = (&OVSNetworkReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + }).SetupWithManager(k8sManager) + Expect(err).NotTo(HaveOccurred()) + + ctx, cancel = context.WithCancel(context.Background()) + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + defer GinkgoRecover() + By("Start controller manager") + err := k8sManager.Start(ctx) + Expect(err).NotTo(HaveOccurred()) + }() + + DeferCleanup(func() { + By("Shutdown controller manager") + cancel() + wg.Wait() + }) + }) + + Context("OVSNetwork", func() { + It("create/delete net-att-def", func() { + netCR := getOvsNetworkCR() + + By("Create OVSNetwork CR") + Expect(k8sClient.Create(ctx, netCR)).NotTo(HaveOccurred()) + DeferCleanup(func() { removeOVSNetwork(ctx, netCR) }) + + By("Check NetworkAttachmentDefinition is created") + Eventually(func(g Gomega) { + netAttDef := &netattdefv1.NetworkAttachmentDefinition{} + g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test", Namespace: testNamespace}, netAttDef)).NotTo(HaveOccurred()) + g.Expect(netAttDef.GetAnnotations()["k8s.v1.cni.cncf.io/resourceName"]).To(ContainSubstring("test")) + }, util.APITimeout, util.RetryInterval).Should(Succeed()) + + By("Remove OVSNetwork CR") + Expect(k8sClient.Delete(ctx, netCR)).NotTo(HaveOccurred()) + + By("Check NetworkAttachmentDefinition is removed") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, types.NamespacedName{Name: "test", + Namespace: testNamespace}, &netattdefv1.NetworkAttachmentDefinition{}) + g.Expect(errors.IsNotFound(err)).To(BeTrue()) + }, util.APITimeout, util.RetryInterval).Should(Succeed()) + }) + It("update net-att-def", func() { + netCR := getOvsNetworkCR() + + By("Create OVSNetwork CR") + Expect(k8sClient.Create(ctx, netCR)).NotTo(HaveOccurred()) + DeferCleanup(func() { removeOVSNetwork(ctx, netCR) }) + + By("Check NetworkAttachmentDefinition is created") + Eventually(func(g Gomega) { + netAttDef := &netattdefv1.NetworkAttachmentDefinition{} + g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test", + Namespace: testNamespace}, netAttDef)).NotTo(HaveOccurred()) + }, util.APITimeout, util.RetryInterval).Should(Succeed()) + + By("Update OVSNetwork CR") + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: netCR.Name, + Namespace: netCR.Namespace}, netCR)).NotTo(HaveOccurred()) + netCR.Spec.Vlan = 200 + Expect(k8sClient.Update(ctx, netCR)).NotTo(HaveOccurred()) + + By("Check NetworkAttachmentDefinition is updated") + Eventually(func(g Gomega) { + netAttDef := &netattdefv1.NetworkAttachmentDefinition{} + g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test", + Namespace: testNamespace}, netAttDef)).NotTo(HaveOccurred()) + g.Expect(netAttDef.Spec.Config).To(ContainSubstring(`"vlan": 200`)) + }, util.APITimeout, util.RetryInterval).Should(Succeed()) + }) + It("re-create net-att-def", func() { + netCR := getOvsNetworkCR() + + By("Create OVSNetwork CR") + Expect(k8sClient.Create(ctx, netCR)).NotTo(HaveOccurred()) + DeferCleanup(func() { removeOVSNetwork(ctx, netCR) }) + + var origUID types.UID + netAttDef := &netattdefv1.NetworkAttachmentDefinition{} + By("Check NetworkAttachmentDefinition is created") + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test", + Namespace: testNamespace}, netAttDef)).NotTo(HaveOccurred()) + origUID = netAttDef.GetUID() + }, util.APITimeout, util.RetryInterval).Should(Succeed()) + + By("Remove NetworkAttachmentDefinition CR") + Expect(k8sClient.Delete(ctx, netAttDef)).NotTo(HaveOccurred()) + + By("Check NetworkAttachmentDefinition is recreated") + Eventually(func(g Gomega) { + netAttDef := &netattdefv1.NetworkAttachmentDefinition{} + g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test", + Namespace: testNamespace}, netAttDef)).NotTo(HaveOccurred()) + g.Expect(netAttDef.GetUID()).NotTo(Equal(origUID)) + }, util.APITimeout, util.RetryInterval).Should(Succeed()) + }) + It("namespace is not yet created", func() { + newNSName := "test-ns" + netCR := getOvsNetworkCR() + netCR.Spec.NetworkNamespace = newNSName + + By("Create OVSNetwork CR") + Expect(k8sClient.Create(ctx, netCR)).NotTo(HaveOccurred()) + DeferCleanup(func() { removeOVSNetwork(ctx, netCR) }) + + // Sleep 3 seconds to be sure the Reconcile loop has been invoked. This can be improved by exposing some information (e.g. the error) + // in the SriovNetwork.Status field. + time.Sleep(3 * time.Second) + + By("Create Namespace") + nsObj := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: newNSName}, + } + Expect(k8sClient.Create(ctx, nsObj)).NotTo(HaveOccurred()) + DeferCleanup(func() { + Expect(k8sClient.Delete(ctx, nsObj)).NotTo(HaveOccurred()) + }) + + By("Check NetworkAttachmentDefinition is created") + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test", Namespace: newNSName}, + &netattdefv1.NetworkAttachmentDefinition{})).NotTo(HaveOccurred()) + }, util.APITimeout, util.RetryInterval).Should(Succeed()) + }) + }) +}) diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 4f42b687c..85f9b6075 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -77,6 +77,10 @@ func setupK8sManagerForTest() (manager.Manager, error) { return []string{o.(*sriovnetworkv1.SriovIBNetwork).Spec.NetworkNamespace} }) + k8sManager.GetCache().IndexField(context.Background(), &sriovnetworkv1.OVSNetwork{}, "spec.networkNamespace", func(o client.Object) []string { + return []string{o.(*sriovnetworkv1.OVSNetwork).Spec.NetworkNamespace} + }) + return k8sManager, nil } diff --git a/main.go b/main.go index b60acdd59..7195c59c3 100644 --- a/main.go +++ b/main.go @@ -163,6 +163,15 @@ func main() { os.Exit(1) } + err = mgrGlobal.GetCache().IndexField(context.Background(), &sriovnetworkv1.OVSNetwork{}, "spec.networkNamespace", func(o client.Object) []string { + return []string{o.(*sriovnetworkv1.OVSNetwork).Spec.NetworkNamespace} + }) + + if err != nil { + setupLog.Error(err, "unable to create index field for cache") + os.Exit(1) + } + if err := initNicIDMap(); err != nil { setupLog.Error(err, "unable to init NicIdMap") os.Exit(1) @@ -194,6 +203,13 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "SriovIBNetwork") os.Exit(1) } + if err = (&controllers.OVSNetworkReconciler{ + Client: mgrGlobal.GetClient(), + Scheme: mgrGlobal.GetScheme(), + }).SetupWithManager(mgrGlobal); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "OVSNetwork") + os.Exit(1) + } if err = (&controllers.SriovNetworkNodePolicyReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), @@ -219,13 +235,6 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "SriovNetworkPoolConfig") os.Exit(1) } - if err = (&controllers.OVSNetworkReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "OVSNetwork") - os.Exit(1) - } // we need a client that doesn't use the local cache for the objects drainKClient, err := client.New(restConfig, client.Options{ From b18f1cb0eb3f46ec9b5e18c2e2fd3fab586e58c8 Mon Sep 17 00:00:00 2001 From: adrianc Date: Mon, 6 May 2024 19:14:31 +0300 Subject: [PATCH 3/3] Update ovs related yamls - set cni version to 1.0.0 when generating network-attachment for ovs network - update rendering test data for ovsnetwork - update ovsnetwork sample to be a valid example Signed-off-by: adrianc --- api/v1/testdata/TestOVSRendering/chained.golden | 2 +- .../testdata/TestOVSRendering/complexconf.golden | 2 +- api/v1/testdata/TestOVSRendering/simpleovs.golden | 2 +- .../manifests/cni-config/ovs/ovs-cni-config.yaml | 2 +- config/samples/sriovnetwork_v1_ovsnetwork.yaml | 14 +++++++++++--- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/api/v1/testdata/TestOVSRendering/chained.golden b/api/v1/testdata/TestOVSRendering/chained.golden index 3589e3daf..42024e90a 100644 --- a/api/v1/testdata/TestOVSRendering/chained.golden +++ b/api/v1/testdata/TestOVSRendering/chained.golden @@ -9,6 +9,6 @@ "namespace": "testnamespace" }, "spec": { - "config": "{ \"cniVersion\":\"0.3.1\", \"name\":\"test\",\"plugins\": [ {\"type\":\"ovs\",\"mtu\":1500,\"ipam\":{} },\n{ \"type\": \"vrf\", \"vrfname\": \"blue\" }\n] }" + "config": "{ \"cniVersion\":\"1.0.0\", \"name\":\"test\",\"plugins\": [ {\"type\":\"ovs\",\"mtu\":1500,\"ipam\":{} },\n{ \"type\": \"vrf\", \"vrfname\": \"blue\" }\n] }" } } diff --git a/api/v1/testdata/TestOVSRendering/complexconf.golden b/api/v1/testdata/TestOVSRendering/complexconf.golden index 9806ba841..4ead9f3f9 100644 --- a/api/v1/testdata/TestOVSRendering/complexconf.golden +++ b/api/v1/testdata/TestOVSRendering/complexconf.golden @@ -9,6 +9,6 @@ "namespace": "testnamespace" }, "spec": { - "config": "{ \"cniVersion\":\"0.3.1\", \"name\":\"test\",\"type\":\"ovs\",\"capabilities\":{\"foo\": \"bar\"},\"bridge\":\"test\",\"vlan\":100,\"mtu\":1500,\"trunk\":[{\"id\":120},{\"minID\":500,\"maxID\":550}],\"interface_type\":\"netdev\",\"ipam\":{\"type\":\"foo\"} }" + "config": "{ \"cniVersion\":\"1.0.0\", \"name\":\"test\",\"type\":\"ovs\",\"capabilities\":{\"foo\": \"bar\"},\"bridge\":\"test\",\"vlan\":100,\"mtu\":1500,\"trunk\":[{\"id\":120},{\"minID\":500,\"maxID\":550}],\"interface_type\":\"netdev\",\"ipam\":{\"type\":\"foo\"} }" } } diff --git a/api/v1/testdata/TestOVSRendering/simpleovs.golden b/api/v1/testdata/TestOVSRendering/simpleovs.golden index 1f286da7c..ce58ef597 100644 --- a/api/v1/testdata/TestOVSRendering/simpleovs.golden +++ b/api/v1/testdata/TestOVSRendering/simpleovs.golden @@ -9,6 +9,6 @@ "namespace": "testnamespace" }, "spec": { - "config": "{ \"cniVersion\":\"0.3.1\", \"name\":\"test\",\"type\":\"ovs\",\"ipam\":{} }" + "config": "{ \"cniVersion\":\"1.0.0\", \"name\":\"test\",\"type\":\"ovs\",\"ipam\":{} }" } } diff --git a/bindata/manifests/cni-config/ovs/ovs-cni-config.yaml b/bindata/manifests/cni-config/ovs/ovs-cni-config.yaml index 30a83b7f3..425e2297f 100644 --- a/bindata/manifests/cni-config/ovs/ovs-cni-config.yaml +++ b/bindata/manifests/cni-config/ovs/ovs-cni-config.yaml @@ -7,7 +7,7 @@ metadata: k8s.v1.cni.cncf.io/resourceName: {{.CniResourceName}} spec: config: '{ - "cniVersion":"0.3.1", + "cniVersion":"1.0.0", "name":"{{.NetworkName}}", {{- if .MetaPluginsConfigured -}} "plugins": [ diff --git a/config/samples/sriovnetwork_v1_ovsnetwork.yaml b/config/samples/sriovnetwork_v1_ovsnetwork.yaml index efe4f57d2..6a81880c0 100644 --- a/config/samples/sriovnetwork_v1_ovsnetwork.yaml +++ b/config/samples/sriovnetwork_v1_ovsnetwork.yaml @@ -1,7 +1,15 @@ apiVersion: sriovnetwork.openshift.io/v1 kind: OVSNetwork metadata: - name: ovsnetwork-sample + name: example-ovsnetwork spec: - # Add fields here - foo: bar + ipam: | + { + "type": "host-local", + "ranges": [[{"subnet": "192.178.0.0/16"}]] + } + networkNamespace: default + resourceName: switchdev-vfs + vlan: 100 + mtu: 1500 +