diff --git a/api/v1/dpunetwork_types.go b/api/v1/dpunetwork_types.go new file mode 100644 index 000000000..094bb072e --- /dev/null +++ b/api/v1/dpunetwork_types.go @@ -0,0 +1,85 @@ +/* +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" +) + +// DpuNetworkSpec defines the desired state of DpuNetwork. +type DpuNetworkSpec struct { + // NodeSelector specifies which nodes this DpuNetwork should apply to. + // If empty, the DpuNetwork will apply to all nodes. + // +optional + NodeSelector *metav1.LabelSelector `json:"nodeSelector,omitempty"` + + // DpuSelector specifies which DPUs (and their VFs) this DpuNetwork targets. + // + // Note: Today this is treated as an opaque selector definition; the controller + // parses vfId ranges from matchExpressions (if present) but does not yet + // validate against a per-VF inventory. + // +optional + DpuSelector *metav1.LabelSelector `json:"dpuSelector,omitempty"` + + // IsAccelerated indicates whether the network should be treated as accelerated + // by downstream components. + // +optional + IsAccelerated bool `json:"isAccelerated,omitempty"` +} + +// DpuNetworkStatus defines the observed state of DpuNetwork. +type DpuNetworkStatus struct { + // Conditions is the status of the DpuNetwork. + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` + + // ResourceName is the Kubernetes extended resource name generated for this network. + // +optional + ResourceName string `json:"resourceName,omitempty"` + + // SelectedVFs is the list of VF IDs parsed from vfId ranges. + // +optional + SelectedVFs []int32 `json:"selectedVFs,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Cluster,shortName=dpunet +//+kubebuilder:printcolumn:name="Resource",type="string",JSONPath=".status.resourceName" +//+kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" + +// DpuNetwork is the Schema for the dpunetworks API. +type DpuNetwork struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec DpuNetworkSpec `json:"spec,omitempty"` + Status DpuNetworkStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// DpuNetworkList contains a list of DpuNetwork. +type DpuNetworkList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []DpuNetwork `json:"items"` +} + +func init() { + SchemeBuilder.Register(&DpuNetwork{}, &DpuNetworkList{}) +} diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 17645862a..0623e9e78 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -215,6 +215,117 @@ func (in *DataProcessingUnitStatus) DeepCopy() *DataProcessingUnitStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DpuNetwork) DeepCopyInto(out *DpuNetwork) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DpuNetwork. +func (in *DpuNetwork) DeepCopy() *DpuNetwork { + if in == nil { + return nil + } + out := new(DpuNetwork) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DpuNetwork) 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 *DpuNetworkList) DeepCopyInto(out *DpuNetworkList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]DpuNetwork, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DpuNetworkList. +func (in *DpuNetworkList) DeepCopy() *DpuNetworkList { + if in == nil { + return nil + } + out := new(DpuNetworkList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DpuNetworkList) 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 *DpuNetworkSpec) DeepCopyInto(out *DpuNetworkSpec) { + *out = *in + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.DpuSelector != nil { + in, out := &in.DpuSelector, &out.DpuSelector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DpuNetworkSpec. +func (in *DpuNetworkSpec) DeepCopy() *DpuNetworkSpec { + if in == nil { + return nil + } + out := new(DpuNetworkSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DpuNetworkStatus) DeepCopyInto(out *DpuNetworkStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SelectedVFs != nil { + in, out := &in.SelectedVFs, &out.SelectedVFs + *out = make([]int32, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DpuNetworkStatus. +func (in *DpuNetworkStatus) DeepCopy() *DpuNetworkStatus { + if in == nil { + return nil + } + out := new(DpuNetworkStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DpuOperatorConfig) DeepCopyInto(out *DpuOperatorConfig) { *out = *in diff --git a/cmd/main.go b/cmd/main.go index 8211d16b6..9261eb5d7 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -133,6 +133,13 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "DataProcessingUnit") os.Exit(1) } + if err := (&controller.DpuNetworkReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "DpuNetwork") + os.Exit(1) + } if os.Getenv("ENABLE_WEBHOOKS") != "false" { if err = (&configv1.DpuOperatorConfig{}).SetupWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "DpuOperatorConfig") diff --git a/config/crd/bases/config.openshift.io_dpunetworks.yaml b/config/crd/bases/config.openshift.io_dpunetworks.yaml new file mode 100644 index 000000000..9e8120fb4 --- /dev/null +++ b/config/crd/bases/config.openshift.io_dpunetworks.yaml @@ -0,0 +1,231 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: dpunetworks.config.openshift.io +spec: + group: config.openshift.io + names: + kind: DpuNetwork + listKind: DpuNetworkList + plural: dpunetworks + shortNames: + - dpunet + singular: dpunetwork + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.resourceName + name: Resource + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + name: v1 + schema: + openAPIV3Schema: + description: DpuNetwork is the Schema for the dpunetworks 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: DpuNetworkSpec defines the desired state of DpuNetwork. + properties: + dpuSelector: + description: |- + DpuSelector specifies which DPUs (and their VFs) this DpuNetwork targets. + + Note: Today this is treated as an opaque selector definition; the controller + parses vfId ranges from matchExpressions (if present) but does not yet + validate against a per-VF inventory. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + isAccelerated: + description: |- + IsAccelerated indicates whether the network should be treated as accelerated + by downstream components. + type: boolean + nodeSelector: + description: |- + NodeSelector specifies which nodes this DpuNetwork should apply to. + If empty, the DpuNetwork will apply to all nodes. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + status: + description: DpuNetworkStatus defines the observed state of DpuNetwork. + properties: + conditions: + description: Conditions is the status of the DpuNetwork. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + resourceName: + description: ResourceName is the Kubernetes extended resource name + generated for this network. + type: string + selectedVFs: + description: SelectedVFs is the list of VF IDs parsed from vfId ranges. + items: + format: int32 + type: integer + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 8f8026c39..f039203de 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -6,6 +6,7 @@ resources: - bases/config.openshift.io_servicefunctionchains.yaml - bases/config.openshift.io_dataprocessingunits.yaml - bases/config.openshift.io_dataprocessingunitconfigs.yaml +- bases/config.openshift.io_dpunetworks.yaml #+kubebuilder:scaffold:crdkustomizeresource patches: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 42ef81004..ab4a2d83c 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -8,6 +8,7 @@ rules: - "" resources: - '*' + - configmaps - serviceaccounts verbs: - create @@ -60,6 +61,7 @@ rules: resources: - dataprocessingunitconfigs - dataprocessingunits + - dpunetworks - dpuoperatorconfigs - servicefunctionchains - servicefunctionchains/finalizers @@ -76,6 +78,7 @@ rules: resources: - dataprocessingunitconfigs/finalizers - dataprocessingunits/finalizers + - dpunetworks/finalizers - dpuoperatorconfigs/finalizers verbs: - update @@ -84,6 +87,7 @@ rules: resources: - dataprocessingunitconfigs/status - dataprocessingunits/status + - dpunetworks/status - dpuoperatorconfigs/status - servicefunctionchains/status verbs: diff --git a/dpu-api/api.proto b/dpu-api/api.proto index df0c8dbc2..591541650 100644 --- a/dpu-api/api.proto +++ b/dpu-api/api.proto @@ -8,6 +8,10 @@ service LifeCycleService { rpc Init(InitRequest) returns (IpPort); } +service DpuNetworkConfigService { + rpc SetDpuNetworkConfig(DpuNetworkConfigRequest) returns (Empty); +} + service NetworkFunctionService { rpc CreateNetworkFunction(NFRequest) returns (Empty); rpc DeleteNetworkFunction(NFRequest) returns (Empty); @@ -26,6 +30,11 @@ message IpPort { message NFRequest { string input = 1; string output = 2; + string bridge_id = 3; +} + +message DpuNetworkConfigRequest { + bool is_accelerated = 1; } message Empty {} diff --git a/dpu-api/gen/api.pb.go b/dpu-api/gen/api.pb.go index e9f87bfb8..425ca4e7e 100644 --- a/dpu-api/gen/api.pb.go +++ b/dpu-api/gen/api.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.6 +// protoc-gen-go v1.36.10 // protoc v3.19.6 // source: api.proto @@ -129,6 +129,7 @@ type NFRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Input string `protobuf:"bytes,1,opt,name=input,proto3" json:"input,omitempty"` Output string `protobuf:"bytes,2,opt,name=output,proto3" json:"output,omitempty"` + BridgeId string `protobuf:"bytes,3,opt,name=bridge_id,json=bridgeId,proto3" json:"bridge_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -177,6 +178,57 @@ func (x *NFRequest) GetOutput() string { return "" } +func (x *NFRequest) GetBridgeId() string { + if x != nil { + return x.BridgeId + } + return "" +} + +type DpuNetworkConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + IsAccelerated bool `protobuf:"varint,1,opt,name=is_accelerated,json=isAccelerated,proto3" json:"is_accelerated,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DpuNetworkConfigRequest) Reset() { + *x = DpuNetworkConfigRequest{} + mi := &file_api_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DpuNetworkConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DpuNetworkConfigRequest) ProtoMessage() {} + +func (x *DpuNetworkConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DpuNetworkConfigRequest.ProtoReflect.Descriptor instead. +func (*DpuNetworkConfigRequest) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{3} +} + +func (x *DpuNetworkConfigRequest) GetIsAccelerated() bool { + if x != nil { + return x.IsAccelerated + } + return false +} + type Empty struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -185,7 +237,7 @@ type Empty struct { func (x *Empty) Reset() { *x = Empty{} - mi := &file_api_proto_msgTypes[3] + mi := &file_api_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -197,7 +249,7 @@ func (x *Empty) String() string { func (*Empty) ProtoMessage() {} func (x *Empty) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[3] + mi := &file_api_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -210,7 +262,7 @@ func (x *Empty) ProtoReflect() protoreflect.Message { // Deprecated: Use Empty.ProtoReflect.Descriptor instead. func (*Empty) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{3} + return file_api_proto_rawDescGZIP(), []int{4} } type VfCount struct { @@ -222,7 +274,7 @@ type VfCount struct { func (x *VfCount) Reset() { *x = VfCount{} - mi := &file_api_proto_msgTypes[4] + mi := &file_api_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -234,7 +286,7 @@ func (x *VfCount) String() string { func (*VfCount) ProtoMessage() {} func (x *VfCount) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[4] + mi := &file_api_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -247,7 +299,7 @@ func (x *VfCount) ProtoReflect() protoreflect.Message { // Deprecated: Use VfCount.ProtoReflect.Descriptor instead. func (*VfCount) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{4} + return file_api_proto_rawDescGZIP(), []int{5} } func (x *VfCount) GetVfCnt() int32 { @@ -266,7 +318,7 @@ type TopologyInfo struct { func (x *TopologyInfo) Reset() { *x = TopologyInfo{} - mi := &file_api_proto_msgTypes[5] + mi := &file_api_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -278,7 +330,7 @@ func (x *TopologyInfo) String() string { func (*TopologyInfo) ProtoMessage() {} func (x *TopologyInfo) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[5] + mi := &file_api_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -291,7 +343,7 @@ func (x *TopologyInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use TopologyInfo.ProtoReflect.Descriptor instead. func (*TopologyInfo) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{5} + return file_api_proto_rawDescGZIP(), []int{6} } func (x *TopologyInfo) GetNode() string { @@ -312,7 +364,7 @@ type Device struct { func (x *Device) Reset() { *x = Device{} - mi := &file_api_proto_msgTypes[6] + mi := &file_api_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -324,7 +376,7 @@ func (x *Device) String() string { func (*Device) ProtoMessage() {} func (x *Device) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[6] + mi := &file_api_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -337,7 +389,7 @@ func (x *Device) ProtoReflect() protoreflect.Message { // Deprecated: Use Device.ProtoReflect.Descriptor instead. func (*Device) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{6} + return file_api_proto_rawDescGZIP(), []int{7} } func (x *Device) GetID() string { @@ -370,7 +422,7 @@ type DeviceListResponse struct { func (x *DeviceListResponse) Reset() { *x = DeviceListResponse{} - mi := &file_api_proto_msgTypes[7] + mi := &file_api_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -382,7 +434,7 @@ func (x *DeviceListResponse) String() string { func (*DeviceListResponse) ProtoMessage() {} func (x *DeviceListResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[7] + mi := &file_api_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -395,7 +447,7 @@ func (x *DeviceListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeviceListResponse.ProtoReflect.Descriptor instead. func (*DeviceListResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{7} + return file_api_proto_rawDescGZIP(), []int{8} } func (x *DeviceListResponse) GetDevices() map[string]*Device { @@ -415,7 +467,7 @@ type PingRequest struct { func (x *PingRequest) Reset() { *x = PingRequest{} - mi := &file_api_proto_msgTypes[8] + mi := &file_api_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -427,7 +479,7 @@ func (x *PingRequest) String() string { func (*PingRequest) ProtoMessage() {} func (x *PingRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[8] + mi := &file_api_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -440,7 +492,7 @@ func (x *PingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PingRequest.ProtoReflect.Descriptor instead. func (*PingRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{8} + return file_api_proto_rawDescGZIP(), []int{9} } func (x *PingRequest) GetTimestamp() int64 { @@ -468,7 +520,7 @@ type PingResponse struct { func (x *PingResponse) Reset() { *x = PingResponse{} - mi := &file_api_proto_msgTypes[9] + mi := &file_api_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -480,7 +532,7 @@ func (x *PingResponse) String() string { func (*PingResponse) ProtoMessage() {} func (x *PingResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[9] + mi := &file_api_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -493,7 +545,7 @@ func (x *PingResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PingResponse.ProtoReflect.Descriptor instead. func (*PingResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{9} + return file_api_proto_rawDescGZIP(), []int{10} } func (x *PingResponse) GetTimestamp() int64 { @@ -527,10 +579,13 @@ const file_api_proto_rawDesc = "" + "\x0edpu_identifier\x18\x02 \x01(\tR\rdpuIdentifier\",\n" + "\x06IpPort\x12\x0e\n" + "\x02ip\x18\x01 \x01(\tR\x02ip\x12\x12\n" + - "\x04port\x18\x02 \x01(\x05R\x04port\"9\n" + + "\x04port\x18\x02 \x01(\x05R\x04port\"V\n" + "\tNFRequest\x12\x14\n" + "\x05input\x18\x01 \x01(\tR\x05input\x12\x16\n" + - "\x06output\x18\x02 \x01(\tR\x06output\"\a\n" + + "\x06output\x18\x02 \x01(\tR\x06output\x12\x1b\n" + + "\tbridge_id\x18\x03 \x01(\tR\bbridgeId\"@\n" + + "\x17DpuNetworkConfigRequest\x12%\n" + + "\x0eis_accelerated\x18\x01 \x01(\bR\risAccelerated\"\a\n" + "\x05Empty\" \n" + "\aVfCount\x12\x15\n" + "\x06vf_cnt\x18\x01 \x01(\x05R\x05vfCnt\"\"\n" + @@ -553,7 +608,9 @@ const file_api_proto_rawDesc = "" + "\fresponder_id\x18\x02 \x01(\tR\vresponderId\x12\x18\n" + "\ahealthy\x18\x03 \x01(\bR\ahealthy2?\n" + "\x10LifeCycleService\x12+\n" + - "\x04Init\x12\x13.Vendor.InitRequest\x1a\x0e.Vendor.IpPort2\x8e\x01\n" + + "\x04Init\x12\x13.Vendor.InitRequest\x1a\x0e.Vendor.IpPort2`\n" + + "\x17DpuNetworkConfigService\x12E\n" + + "\x13SetDpuNetworkConfig\x12\x1f.Vendor.DpuNetworkConfigRequest\x1a\r.Vendor.Empty2\x8e\x01\n" + "\x16NetworkFunctionService\x129\n" + "\x15CreateNetworkFunction\x12\x11.Vendor.NFRequest\x1a\r.Vendor.Empty\x129\n" + "\x15DeleteNetworkFunction\x12\x11.Vendor.NFRequest\x1a\r.Vendor.Empty2w\n" + @@ -576,38 +633,41 @@ func file_api_proto_rawDescGZIP() []byte { return file_api_proto_rawDescData } -var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_api_proto_goTypes = []any{ - (*InitRequest)(nil), // 0: Vendor.InitRequest - (*IpPort)(nil), // 1: Vendor.IpPort - (*NFRequest)(nil), // 2: Vendor.NFRequest - (*Empty)(nil), // 3: Vendor.Empty - (*VfCount)(nil), // 4: Vendor.VfCount - (*TopologyInfo)(nil), // 5: Vendor.TopologyInfo - (*Device)(nil), // 6: Vendor.Device - (*DeviceListResponse)(nil), // 7: Vendor.DeviceListResponse - (*PingRequest)(nil), // 8: Vendor.PingRequest - (*PingResponse)(nil), // 9: Vendor.PingResponse - nil, // 10: Vendor.DeviceListResponse.DevicesEntry + (*InitRequest)(nil), // 0: Vendor.InitRequest + (*IpPort)(nil), // 1: Vendor.IpPort + (*NFRequest)(nil), // 2: Vendor.NFRequest + (*DpuNetworkConfigRequest)(nil), // 3: Vendor.DpuNetworkConfigRequest + (*Empty)(nil), // 4: Vendor.Empty + (*VfCount)(nil), // 5: Vendor.VfCount + (*TopologyInfo)(nil), // 6: Vendor.TopologyInfo + (*Device)(nil), // 7: Vendor.Device + (*DeviceListResponse)(nil), // 8: Vendor.DeviceListResponse + (*PingRequest)(nil), // 9: Vendor.PingRequest + (*PingResponse)(nil), // 10: Vendor.PingResponse + nil, // 11: Vendor.DeviceListResponse.DevicesEntry } var file_api_proto_depIdxs = []int32{ - 5, // 0: Vendor.Device.topology:type_name -> Vendor.TopologyInfo - 10, // 1: Vendor.DeviceListResponse.devices:type_name -> Vendor.DeviceListResponse.DevicesEntry - 6, // 2: Vendor.DeviceListResponse.DevicesEntry.value:type_name -> Vendor.Device + 6, // 0: Vendor.Device.topology:type_name -> Vendor.TopologyInfo + 11, // 1: Vendor.DeviceListResponse.devices:type_name -> Vendor.DeviceListResponse.DevicesEntry + 7, // 2: Vendor.DeviceListResponse.DevicesEntry.value:type_name -> Vendor.Device 0, // 3: Vendor.LifeCycleService.Init:input_type -> Vendor.InitRequest - 2, // 4: Vendor.NetworkFunctionService.CreateNetworkFunction:input_type -> Vendor.NFRequest - 2, // 5: Vendor.NetworkFunctionService.DeleteNetworkFunction:input_type -> Vendor.NFRequest - 3, // 6: Vendor.DeviceService.GetDevices:input_type -> Vendor.Empty - 4, // 7: Vendor.DeviceService.SetNumVfs:input_type -> Vendor.VfCount - 8, // 8: Vendor.HeartbeatService.Ping:input_type -> Vendor.PingRequest - 1, // 9: Vendor.LifeCycleService.Init:output_type -> Vendor.IpPort - 3, // 10: Vendor.NetworkFunctionService.CreateNetworkFunction:output_type -> Vendor.Empty - 3, // 11: Vendor.NetworkFunctionService.DeleteNetworkFunction:output_type -> Vendor.Empty - 7, // 12: Vendor.DeviceService.GetDevices:output_type -> Vendor.DeviceListResponse - 4, // 13: Vendor.DeviceService.SetNumVfs:output_type -> Vendor.VfCount - 9, // 14: Vendor.HeartbeatService.Ping:output_type -> Vendor.PingResponse - 9, // [9:15] is the sub-list for method output_type - 3, // [3:9] is the sub-list for method input_type + 3, // 4: Vendor.DpuNetworkConfigService.SetDpuNetworkConfig:input_type -> Vendor.DpuNetworkConfigRequest + 2, // 5: Vendor.NetworkFunctionService.CreateNetworkFunction:input_type -> Vendor.NFRequest + 2, // 6: Vendor.NetworkFunctionService.DeleteNetworkFunction:input_type -> Vendor.NFRequest + 4, // 7: Vendor.DeviceService.GetDevices:input_type -> Vendor.Empty + 5, // 8: Vendor.DeviceService.SetNumVfs:input_type -> Vendor.VfCount + 9, // 9: Vendor.HeartbeatService.Ping:input_type -> Vendor.PingRequest + 1, // 10: Vendor.LifeCycleService.Init:output_type -> Vendor.IpPort + 4, // 11: Vendor.DpuNetworkConfigService.SetDpuNetworkConfig:output_type -> Vendor.Empty + 4, // 12: Vendor.NetworkFunctionService.CreateNetworkFunction:output_type -> Vendor.Empty + 4, // 13: Vendor.NetworkFunctionService.DeleteNetworkFunction:output_type -> Vendor.Empty + 8, // 14: Vendor.DeviceService.GetDevices:output_type -> Vendor.DeviceListResponse + 5, // 15: Vendor.DeviceService.SetNumVfs:output_type -> Vendor.VfCount + 10, // 16: Vendor.HeartbeatService.Ping:output_type -> Vendor.PingResponse + 10, // [10:17] is the sub-list for method output_type + 3, // [3:10] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name @@ -624,9 +684,9 @@ func file_api_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_rawDesc), len(file_api_proto_rawDesc)), NumEnums: 0, - NumMessages: 11, + NumMessages: 12, NumExtensions: 0, - NumServices: 4, + NumServices: 5, }, GoTypes: file_api_proto_goTypes, DependencyIndexes: file_api_proto_depIdxs, diff --git a/dpu-api/gen/api_grpc.pb.go b/dpu-api/gen/api_grpc.pb.go index 96ce737b7..90e67fbc2 100644 --- a/dpu-api/gen/api_grpc.pb.go +++ b/dpu-api/gen/api_grpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.5.1 +// - protoc-gen-go-grpc v1.6.1 // - protoc v3.19.6 // source: api.proto @@ -63,7 +63,7 @@ type LifeCycleServiceServer interface { type UnimplementedLifeCycleServiceServer struct{} func (UnimplementedLifeCycleServiceServer) Init(context.Context, *InitRequest) (*IpPort, error) { - return nil, status.Errorf(codes.Unimplemented, "method Init not implemented") + return nil, status.Error(codes.Unimplemented, "method Init not implemented") } func (UnimplementedLifeCycleServiceServer) mustEmbedUnimplementedLifeCycleServiceServer() {} func (UnimplementedLifeCycleServiceServer) testEmbeddedByValue() {} @@ -76,7 +76,7 @@ type UnsafeLifeCycleServiceServer interface { } func RegisterLifeCycleServiceServer(s grpc.ServiceRegistrar, srv LifeCycleServiceServer) { - // If the following call pancis, it indicates UnimplementedLifeCycleServiceServer was + // If the following call panics, it indicates UnimplementedLifeCycleServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. @@ -120,6 +120,109 @@ var LifeCycleService_ServiceDesc = grpc.ServiceDesc{ Metadata: "api.proto", } +const ( + DpuNetworkConfigService_SetDpuNetworkConfig_FullMethodName = "/Vendor.DpuNetworkConfigService/SetDpuNetworkConfig" +) + +// DpuNetworkConfigServiceClient is the client API for DpuNetworkConfigService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type DpuNetworkConfigServiceClient interface { + SetDpuNetworkConfig(ctx context.Context, in *DpuNetworkConfigRequest, opts ...grpc.CallOption) (*Empty, error) +} + +type dpuNetworkConfigServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewDpuNetworkConfigServiceClient(cc grpc.ClientConnInterface) DpuNetworkConfigServiceClient { + return &dpuNetworkConfigServiceClient{cc} +} + +func (c *dpuNetworkConfigServiceClient) SetDpuNetworkConfig(ctx context.Context, in *DpuNetworkConfigRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Empty) + err := c.cc.Invoke(ctx, DpuNetworkConfigService_SetDpuNetworkConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// DpuNetworkConfigServiceServer is the server API for DpuNetworkConfigService service. +// All implementations must embed UnimplementedDpuNetworkConfigServiceServer +// for forward compatibility. +type DpuNetworkConfigServiceServer interface { + SetDpuNetworkConfig(context.Context, *DpuNetworkConfigRequest) (*Empty, error) + mustEmbedUnimplementedDpuNetworkConfigServiceServer() +} + +// UnimplementedDpuNetworkConfigServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedDpuNetworkConfigServiceServer struct{} + +func (UnimplementedDpuNetworkConfigServiceServer) SetDpuNetworkConfig(context.Context, *DpuNetworkConfigRequest) (*Empty, error) { + return nil, status.Error(codes.Unimplemented, "method SetDpuNetworkConfig not implemented") +} +func (UnimplementedDpuNetworkConfigServiceServer) mustEmbedUnimplementedDpuNetworkConfigServiceServer() { +} +func (UnimplementedDpuNetworkConfigServiceServer) testEmbeddedByValue() {} + +// UnsafeDpuNetworkConfigServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to DpuNetworkConfigServiceServer will +// result in compilation errors. +type UnsafeDpuNetworkConfigServiceServer interface { + mustEmbedUnimplementedDpuNetworkConfigServiceServer() +} + +func RegisterDpuNetworkConfigServiceServer(s grpc.ServiceRegistrar, srv DpuNetworkConfigServiceServer) { + // If the following call panics, it indicates UnimplementedDpuNetworkConfigServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&DpuNetworkConfigService_ServiceDesc, srv) +} + +func _DpuNetworkConfigService_SetDpuNetworkConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DpuNetworkConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DpuNetworkConfigServiceServer).SetDpuNetworkConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DpuNetworkConfigService_SetDpuNetworkConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DpuNetworkConfigServiceServer).SetDpuNetworkConfig(ctx, req.(*DpuNetworkConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// DpuNetworkConfigService_ServiceDesc is the grpc.ServiceDesc for DpuNetworkConfigService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var DpuNetworkConfigService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "Vendor.DpuNetworkConfigService", + HandlerType: (*DpuNetworkConfigServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SetDpuNetworkConfig", + Handler: _DpuNetworkConfigService_SetDpuNetworkConfig_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api.proto", +} + const ( NetworkFunctionService_CreateNetworkFunction_FullMethodName = "/Vendor.NetworkFunctionService/CreateNetworkFunction" NetworkFunctionService_DeleteNetworkFunction_FullMethodName = "/Vendor.NetworkFunctionService/DeleteNetworkFunction" @@ -178,10 +281,10 @@ type NetworkFunctionServiceServer interface { type UnimplementedNetworkFunctionServiceServer struct{} func (UnimplementedNetworkFunctionServiceServer) CreateNetworkFunction(context.Context, *NFRequest) (*Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method CreateNetworkFunction not implemented") + return nil, status.Error(codes.Unimplemented, "method CreateNetworkFunction not implemented") } func (UnimplementedNetworkFunctionServiceServer) DeleteNetworkFunction(context.Context, *NFRequest) (*Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method DeleteNetworkFunction not implemented") + return nil, status.Error(codes.Unimplemented, "method DeleteNetworkFunction not implemented") } func (UnimplementedNetworkFunctionServiceServer) mustEmbedUnimplementedNetworkFunctionServiceServer() { } @@ -195,7 +298,7 @@ type UnsafeNetworkFunctionServiceServer interface { } func RegisterNetworkFunctionServiceServer(s grpc.ServiceRegistrar, srv NetworkFunctionServiceServer) { - // If the following call pancis, it indicates UnimplementedNetworkFunctionServiceServer was + // If the following call panics, it indicates UnimplementedNetworkFunctionServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. @@ -319,10 +422,10 @@ type DeviceServiceServer interface { type UnimplementedDeviceServiceServer struct{} func (UnimplementedDeviceServiceServer) GetDevices(context.Context, *Empty) (*DeviceListResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetDevices not implemented") + return nil, status.Error(codes.Unimplemented, "method GetDevices not implemented") } func (UnimplementedDeviceServiceServer) SetNumVfs(context.Context, *VfCount) (*VfCount, error) { - return nil, status.Errorf(codes.Unimplemented, "method SetNumVfs not implemented") + return nil, status.Error(codes.Unimplemented, "method SetNumVfs not implemented") } func (UnimplementedDeviceServiceServer) mustEmbedUnimplementedDeviceServiceServer() {} func (UnimplementedDeviceServiceServer) testEmbeddedByValue() {} @@ -335,7 +438,7 @@ type UnsafeDeviceServiceServer interface { } func RegisterDeviceServiceServer(s grpc.ServiceRegistrar, srv DeviceServiceServer) { - // If the following call pancis, it indicates UnimplementedDeviceServiceServer was + // If the following call panics, it indicates UnimplementedDeviceServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. @@ -446,7 +549,7 @@ type HeartbeatServiceServer interface { type UnimplementedHeartbeatServiceServer struct{} func (UnimplementedHeartbeatServiceServer) Ping(context.Context, *PingRequest) (*PingResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented") + return nil, status.Error(codes.Unimplemented, "method Ping not implemented") } func (UnimplementedHeartbeatServiceServer) mustEmbedUnimplementedHeartbeatServiceServer() {} func (UnimplementedHeartbeatServiceServer) testEmbeddedByValue() {} @@ -459,7 +562,7 @@ type UnsafeHeartbeatServiceServer interface { } func RegisterHeartbeatServiceServer(s grpc.ServiceRegistrar, srv HeartbeatServiceServer) { - // If the following call pancis, it indicates UnimplementedHeartbeatServiceServer was + // If the following call panics, it indicates UnimplementedHeartbeatServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. diff --git a/dpu-cni/pkgs/cnitypes/cnitypes.go b/dpu-cni/pkgs/cnitypes/cnitypes.go index 65a342ee6..6a56b3667 100644 --- a/dpu-cni/pkgs/cnitypes/cnitypes.go +++ b/dpu-cni/pkgs/cnitypes/cnitypes.go @@ -130,6 +130,8 @@ type NetConf struct { RuntimeConfig struct { Mac string `json:"mac,omitempty"` } `json:"runtimeConfig,omitempty"` - LogLevel string `json:"logLevel,omitempty"` - LogFile string `json:"logFile,omitempty"` + LogLevel string `json:"logLevel,omitempty"` + LogFile string `json:"logFile,omitempty"` + BridgeID string `json:"bridgeID,omitempty"` + IsAccelerated bool `json:"isAccelerated,omitempty"` } diff --git a/dpu-cni/pkgs/networkfn/networkfn.go b/dpu-cni/pkgs/networkfn/networkfn.go index 3b29a1ca4..7532dfdac 100644 --- a/dpu-cni/pkgs/networkfn/networkfn.go +++ b/dpu-cni/pkgs/networkfn/networkfn.go @@ -322,11 +322,18 @@ func CmdDel(req *cnitypes.PodRequest) error { conf := req.CNIConf if req.Netns == "" { + klog.Warning("CmdDel: netns is empty, device may be orphaned") return nil } containerNs, err := ns.GetNS(req.Netns) if err != nil { + if _, ok := err.(ns.NSPathNotExistErr); ok { + klog.Warningf("CmdDel: netns %q no longer exists for device %s — attempting recovery", + req.Netns, conf.DeviceID) + recoverOrphanedDevice(conf.DeviceID) + return nil + } return fmt.Errorf("failed to open netns %q: %v", req.Netns, err) } defer containerNs.Close() @@ -347,3 +354,41 @@ func CmdDel(req *cnitypes.PodRequest) error { return nil } + +// recoverOrphanedDevice attempts to find a device that was left behind in a +// now-destroyed namespace and restore it to the init namespace. +// Hardware-backed VF representors survive namespace destruction (unlike veths +// which are auto-cleaned by the kernel), so they may be found under the +// temporary name assigned by moveLinkInNetNamespace with the original name +// stored in the device's alias. +// For veth pairs this is a safe no-op: the kernel already destroyed both ends. +func recoverOrphanedDevice(deviceName string) { + if _, err := netlink.LinkByName(deviceName); err == nil { + klog.Infof("recoverOrphanedDevice: %s already in init namespace", deviceName) + return + } + + links, err := netlink.LinkList() + if err != nil { + klog.Errorf("recoverOrphanedDevice: failed to list links: %v", err) + return + } + for _, link := range links { + if link.Attrs().Alias == deviceName { + klog.Infof("recoverOrphanedDevice: found %s under temp name %s, renaming back", + deviceName, link.Attrs().Name) + if err := netlink.LinkSetName(link, deviceName); err != nil { + klog.Errorf("recoverOrphanedDevice: failed to rename %s to %s: %v", + link.Attrs().Name, deviceName, err) + return + } + if err := netlink.LinkSetUp(link); err != nil { + klog.Warningf("recoverOrphanedDevice: failed to bring up %s: %v", deviceName, err) + } + return + } + } + + klog.Warningf("recoverOrphanedDevice: %s not found in init namespace — "+ + "device was likely a veth pair already cleaned up by the kernel", deviceName) +} diff --git a/examples/dpunetwork-net1.yaml b/examples/dpunetwork-net1.yaml new file mode 100644 index 000000000..a5ceb9b44 --- /dev/null +++ b/examples/dpunetwork-net1.yaml @@ -0,0 +1,16 @@ +apiVersion: config.openshift.io/v1 +kind: DpuNetwork +metadata: + name: net1 +spec: + dpuSelector: + matchExpressions: + - key: vfId + operator: In + values: + - "0-3" + nodeSelector: + matchLabels: + node-role.kubernetes.io/worker: "" + isAccelerated: true + diff --git a/examples/host-pod.yaml b/examples/host-pod.yaml new file mode 100644 index 000000000..204fc66d8 --- /dev/null +++ b/examples/host-pod.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Pod +metadata: + name: host-pod + namespace: default + annotations: + dpu.config.openshift.io/dpu-network: net1 +spec: + nodeSelector: + node-role.kubernetes.io/worker: "" + containers: + - name: app-container + image: ghcr.io/ovn-kubernetes/kubernetes-traffic-flow-tests:latest + resources: + requests: + openshift.io/dpunetwork-net1: "1" + limits: + openshift.io/dpunetwork-net1: "1" diff --git a/examples/nf-pod.yaml b/examples/nf-pod.yaml new file mode 100644 index 000000000..66720e7d5 --- /dev/null +++ b/examples/nf-pod.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nf-pod + namespace: default + annotations: + dpu.config.openshift.io/dpu-network: net1 +spec: + nodeSelector: + dpu.config.openshift.io/dpuside: "dpu" + containers: + - name: nf-container + image: ghcr.io/ovn-kubernetes/kubernetes-traffic-flow-tests:latest + securityContext: + capabilities: + add: ["NET_ADMIN", "NET_RAW"] + resources: + requests: + openshift.io/dpunetwork-net1: "4" + openshift.io/dpu-accelerated: "1" + limits: + openshift.io/dpunetwork-net1: "4" + openshift.io/dpu-accelerated: "1" diff --git a/internal/controller/bindata/daemon/02.daemon_role.yaml b/internal/controller/bindata/daemon/02.daemon_role.yaml index 80add9bcc..69e6d4d9f 100644 --- a/internal/controller/bindata/daemon/02.daemon_role.yaml +++ b/internal/controller/bindata/daemon/02.daemon_role.yaml @@ -25,6 +25,14 @@ rules: - patch - update - watch +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch - apiGroups: - "" resources: diff --git a/internal/controller/dpunetwork_controller.go b/internal/controller/dpunetwork_controller.go new file mode 100644 index 000000000..1f35011f8 --- /dev/null +++ b/internal/controller/dpunetwork_controller.go @@ -0,0 +1,471 @@ +package controller + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "sort" + "strconv" + "strings" + + netattdefv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + configv1 "github.com/openshift/dpu-operator/api/v1" + "github.com/openshift/dpu-operator/pkgs/vars" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +const ( + dpuDevicePluginConfigMapName = "dpu-device-plugin-config" + defaultDpuNetworkNADNamespace = "default" + dpuNetworkFinalizer = "config.openshift.io/dpunetwork-cleanup" + dpuAccelNADName = "dpu-accel-nad" +) + +type dpuDevicePluginConfig struct { + Resources []dpuDevicePluginResource `json:"resources"` +} + +type dpuDevicePluginResource struct { + ResourceName string `json:"resourceName"` + DpuNetworkName string `json:"dpuNetworkName"` + NodeSelector *metav1.LabelSelector `json:"nodeSelector,omitempty"` + VfRanges []string `json:"vfRanges,omitempty"` + IsAccelerated bool `json:"isAccelerated,omitempty"` +} + +type dpuNetworkNADConfig struct { + Type string `json:"type"` + CNIVersion string `json:"cniVersion"` + Name string `json:"name"` + BridgeID string `json:"bridgeID"` + IsAccelerated bool `json:"isAccelerated,omitempty"` +} + +// DpuNetworkReconciler reconciles a DpuNetwork object +type DpuNetworkReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=config.openshift.io,resources=dpunetworks,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=config.openshift.io,resources=dpunetworks/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=config.openshift.io,resources=dpunetworks/finalizers,verbs=update +//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=k8s.cni.cncf.io,resources=network-attachment-definitions,verbs=get;list;watch;create;update;patch;delete + +func (r *DpuNetworkReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + logger := log.FromContext(ctx) + + net := &configv1.DpuNetwork{} + if err := r.Get(ctx, req.NamespacedName, net); err != nil { + if apierrors.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + // Handle deletion: remove this network's entry from the shared ConfigMap, + // then remove the finalizer so Kubernetes can delete the CR. + if !net.DeletionTimestamp.IsZero() { + if controllerutil.ContainsFinalizer(net, dpuNetworkFinalizer) { + if err := r.removeFromDevicePluginConfigMap(ctx, net.Name); err != nil { + logger.Error(err, "Failed to clean up ConfigMap entry on deletion") + return ctrl.Result{}, err + } + if err := r.deleteNAD(ctx, net.Name); err != nil { + logger.Error(err, "Failed to delete NAD on deletion") + return ctrl.Result{}, err + } + if err := r.deleteAccelNADIfUnused(ctx, net.Name); err != nil { + logger.Error(err, "Failed to delete accelerated NAD on deletion") + return ctrl.Result{}, err + } + controllerutil.RemoveFinalizer(net, dpuNetworkFinalizer) + if err := r.Update(ctx, net); err != nil { + return ctrl.Result{}, err + } + } + return ctrl.Result{}, nil + } + + // Ensure finalizer is present for cleanup on deletion. + if !controllerutil.ContainsFinalizer(net, dpuNetworkFinalizer) { + controllerutil.AddFinalizer(net, dpuNetworkFinalizer) + if err := r.Update(ctx, net); err != nil { + return ctrl.Result{}, err + } + } + + resourceName := fmt.Sprintf("openshift.io/dpunetwork-%s", net.Name) + selectedVFs, vfRanges := parseVfRangesFromSelector(net.Spec.DpuSelector) + + if meta.FindStatusCondition(net.Status.Conditions, "Ready") == nil { + meta.SetStatusCondition(&net.Status.Conditions, metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionFalse, + Reason: "Reconciling", + Message: "Reconciling DpuNetwork", + }) + } + + // Ensure ConfigMap used by per-node daemons/device plugins. + if err := r.ensureDevicePluginConfigMap(ctx, net, resourceName, vfRanges); err != nil { + meta.SetStatusCondition(&net.Status.Conditions, metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionFalse, + Reason: "ConfigMapError", + Message: err.Error(), + }) + _ = r.Status().Update(ctx, net) + return ctrl.Result{}, err + } + + // Ensure NetworkAttachmentDefinition for this network. + if err := r.ensureNAD(ctx, net, resourceName); err != nil { + meta.SetStatusCondition(&net.Status.Conditions, metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionFalse, + Reason: "NADError", + Message: err.Error(), + }) + _ = r.Status().Update(ctx, net) + return ctrl.Result{}, err + } + + // Ensure a single global accelerated NAD if this network uses acceleration. + if net.Spec.IsAccelerated { + if err := r.ensureAccelNAD(ctx); err != nil { + meta.SetStatusCondition(&net.Status.Conditions, metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionFalse, + Reason: "AccelNADError", + Message: err.Error(), + }) + _ = r.Status().Update(ctx, net) + return ctrl.Result{}, err + } + } + + net.Status.ResourceName = resourceName + net.Status.SelectedVFs = selectedVFs + meta.SetStatusCondition(&net.Status.Conditions, metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionTrue, + Reason: "ComponentsReady", + Message: "ConfigMap and NAD ensured", + }) + + if err := r.Status().Update(ctx, net); err != nil { + logger.Error(err, "Failed to update DpuNetwork status") + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +func (r *DpuNetworkReconciler) ensureDevicePluginConfigMap(ctx context.Context, net *configv1.DpuNetwork, resourceName string, vfRanges []string) error { + cm := &corev1.ConfigMap{} + key := types.NamespacedName{Name: dpuDevicePluginConfigMapName, Namespace: vars.Namespace} + err := r.Get(ctx, key, cm) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + if apierrors.IsNotFound(err) { + cm = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: key.Name, Namespace: key.Namespace}} + } + + // Build config.json payload. For now, we append one resource entry per DpuNetwork. + cfg := dpuDevicePluginConfig{} + if cm.Data != nil { + if raw := cm.Data["config.json"]; raw != "" { + if err := json.Unmarshal([]byte(raw), &cfg); err != nil { + return fmt.Errorf("failed to parse existing ConfigMap config.json: %w", err) + } + } + } + + // Upsert entry for this DpuNetwork. + newEntry := dpuDevicePluginResource{ + ResourceName: resourceName, + DpuNetworkName: net.Name, + NodeSelector: net.Spec.NodeSelector, + VfRanges: vfRanges, + IsAccelerated: net.Spec.IsAccelerated, + } + var out []dpuDevicePluginResource + for _, e := range cfg.Resources { + if e.DpuNetworkName == net.Name { + continue + } + out = append(out, e) + } + out = append(out, newEntry) + // stable ordering + sort.Slice(out, func(i, j int) bool { return out[i].DpuNetworkName < out[j].DpuNetworkName }) + cfg.Resources = out + + payload, err := json.MarshalIndent(cfg, "", " ") + if err != nil { + return err + } + if cm.Data == nil { + cm.Data = map[string]string{} + } + cm.Data["config.json"] = string(payload) + + // Owner reference: the ConfigMap is shared across all DpuNetwork CRs, so we do NOT set controller reference. + // (Multiple controller references would be invalid.) + + if cm.CreationTimestamp.IsZero() { + return r.Create(ctx, cm) + } + return r.Update(ctx, cm) +} + +func (r *DpuNetworkReconciler) removeFromDevicePluginConfigMap(ctx context.Context, networkName string) error { + cm := &corev1.ConfigMap{} + key := types.NamespacedName{Name: dpuDevicePluginConfigMapName, Namespace: vars.Namespace} + if err := r.Get(ctx, key, cm); err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return err + } + + cfg := dpuDevicePluginConfig{} + if cm.Data != nil { + if raw := cm.Data["config.json"]; raw != "" { + if err := json.Unmarshal([]byte(raw), &cfg); err != nil { + return fmt.Errorf("failed to parse existing ConfigMap config.json: %w", err) + } + } + } + + var out []dpuDevicePluginResource + for _, e := range cfg.Resources { + if e.DpuNetworkName == networkName { + continue + } + out = append(out, e) + } + + if len(out) == len(cfg.Resources) { + return nil + } + + cfg.Resources = out + if len(out) == 0 { + // All networks removed; delete the ConfigMap so the daemon + // transitions back to the default device plugin. + return r.Delete(ctx, cm) + } + + payload, err := json.MarshalIndent(cfg, "", " ") + if err != nil { + return err + } + cm.Data["config.json"] = string(payload) + return r.Update(ctx, cm) +} + +func (r *DpuNetworkReconciler) ensureNAD(ctx context.Context, net *configv1.DpuNetwork, resourceName string) error { + bridgeID := stableBridgeID(net.Name) + nadName := fmt.Sprintf("%s-nad", net.Name) + key := types.NamespacedName{Name: nadName, Namespace: defaultDpuNetworkNADNamespace} + + nad := &netattdefv1.NetworkAttachmentDefinition{} + err := r.Get(ctx, key, nad) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + if apierrors.IsNotFound(err) { + nad = &netattdefv1.NetworkAttachmentDefinition{ObjectMeta: metav1.ObjectMeta{Name: key.Name, Namespace: key.Namespace}} + } + + if nad.Annotations == nil { + nad.Annotations = map[string]string{} + } + nad.Annotations["dpu.config.openshift.io/dpu-network"] = net.Name + nad.Annotations["k8s.v1.cni.cncf.io/resourceName"] = resourceName + + nadConfig := dpuNetworkNADConfig{ + Type: "dpu-cni", + CNIVersion: "0.4.0", + Name: "dpu-cni", + BridgeID: bridgeID, + IsAccelerated: net.Spec.IsAccelerated, + } + configBytes, err := json.Marshal(nadConfig) + if err != nil { + return fmt.Errorf("failed to marshal NAD config: %w", err) + } + nad.Spec.Config = string(configBytes) + + if nad.CreationTimestamp.IsZero() { + return r.Create(ctx, nad) + } + return r.Update(ctx, nad) +} + +func (r *DpuNetworkReconciler) deleteNAD(ctx context.Context, networkName string) error { + nadName := fmt.Sprintf("%s-nad", networkName) + nad := &netattdefv1.NetworkAttachmentDefinition{} + key := types.NamespacedName{Name: nadName, Namespace: defaultDpuNetworkNADNamespace} + if err := r.Get(ctx, key, nad); err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return err + } + return r.Delete(ctx, nad) +} + +func (r *DpuNetworkReconciler) ensureAccelNAD(ctx context.Context) error { + key := types.NamespacedName{Name: dpuAccelNADName, Namespace: defaultDpuNetworkNADNamespace} + + nad := &netattdefv1.NetworkAttachmentDefinition{} + err := r.Get(ctx, key, nad) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + if apierrors.IsNotFound(err) { + nad = &netattdefv1.NetworkAttachmentDefinition{ObjectMeta: metav1.ObjectMeta{Name: key.Name, Namespace: key.Namespace}} + } + + if nad.Annotations == nil { + nad.Annotations = map[string]string{} + } + nad.Annotations["k8s.v1.cni.cncf.io/resourceName"] = "openshift.io/dpu-accelerated" + + nadConfig := dpuNetworkNADConfig{ + Type: "dpu-cni", + CNIVersion: "0.4.0", + Name: "dpu-cni", + IsAccelerated: true, + } + configBytes, err := json.Marshal(nadConfig) + if err != nil { + return fmt.Errorf("failed to marshal accelerated NAD config: %w", err) + } + nad.Spec.Config = string(configBytes) + + if nad.CreationTimestamp.IsZero() { + return r.Create(ctx, nad) + } + return r.Update(ctx, nad) +} + +func (r *DpuNetworkReconciler) deleteAccelNADIfUnused(ctx context.Context, deletingNetwork string) error { + list := &configv1.DpuNetworkList{} + if err := r.List(ctx, list); err != nil { + return err + } + for _, n := range list.Items { + if n.Name == deletingNetwork { + continue + } + if n.Spec.IsAccelerated { + return nil + } + } + + nad := &netattdefv1.NetworkAttachmentDefinition{} + key := types.NamespacedName{Name: dpuAccelNADName, Namespace: defaultDpuNetworkNADNamespace} + if err := r.Get(ctx, key, nad); err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return err + } + return r.Delete(ctx, nad) +} + +func stableBridgeID(name string) string { + sum := sha256.Sum256([]byte(name)) + // keep short but collision-resistant enough for demo purposes + return hex.EncodeToString(sum[:])[:8] +} + +func parseVfRangesFromSelector(sel *metav1.LabelSelector) ([]int32, []string) { + if sel == nil { + return nil, nil + } + + var ranges []string + for _, expr := range sel.MatchExpressions { + if expr.Key != "vfId" { + continue + } + if strings.ToLower(string(expr.Operator)) != "in" { + // only handle In for now + continue + } + for _, v := range expr.Values { + v = strings.TrimSpace(v) + if v == "" { + continue + } + ranges = append(ranges, v) + } + } + + vfSet := map[int32]struct{}{} + for _, r := range ranges { + for _, id := range expandRange(r) { + vfSet[id] = struct{}{} + } + } + + var vfs []int32 + for id := range vfSet { + vfs = append(vfs, id) + } + sort.Slice(vfs, func(i, j int) bool { return vfs[i] < vfs[j] }) + return vfs, ranges +} + +func expandRange(s string) []int32 { + // supports "N" or "A-B" + if !strings.Contains(s, "-") { + v, err := strconv.Atoi(s) + if err != nil { + return nil + } + return []int32{int32(v)} + } + parts := strings.SplitN(s, "-", 2) + if len(parts) != 2 { + return nil + } + start, err1 := strconv.Atoi(strings.TrimSpace(parts[0])) + end, err2 := strconv.Atoi(strings.TrimSpace(parts[1])) + if err1 != nil || err2 != nil { + return nil + } + if end < start { + start, end = end, start + } + out := make([]int32, 0, end-start+1) + for i := start; i <= end; i++ { + out = append(out, int32(i)) + } + return out +} + +// SetupWithManager sets up the controller with the Manager. +func (r *DpuNetworkReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&configv1.DpuNetwork{}). + Complete(r) +} diff --git a/internal/controller/dpunetwork_controller_test.go b/internal/controller/dpunetwork_controller_test.go new file mode 100644 index 000000000..4f4ab7168 --- /dev/null +++ b/internal/controller/dpunetwork_controller_test.go @@ -0,0 +1,192 @@ +/* +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 controller + +import ( + "context" + "encoding/json" + "os" + "sync" + + netattdefv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + configv1 "github.com/openshift/dpu-operator/api/v1" + "github.com/openshift/dpu-operator/internal/scheme" + "github.com/openshift/dpu-operator/internal/testutils" + "github.com/openshift/dpu-operator/pkgs/vars" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +type testDevicePluginConfig struct { + Resources []testDevicePluginResource `json:"resources"` +} + +type testDevicePluginResource struct { + ResourceName string `json:"resourceName"` + DpuNetworkName string `json:"dpuNetworkName"` +} + +func startDpuNetworkControllerManager(ctx context.Context, client *rest.Config, wg *sync.WaitGroup) ctrl.Manager { + mgr, err := ctrl.NewManager(client, ctrl.Options{ + Scheme: scheme.Scheme, + Metrics: server.Options{ + BindAddress: ":18002", + }, + WebhookServer: webhook.NewServer(webhook.Options{Port: 9444}), + LeaderElectionID: "dpunetwork-controller-test.openshift.io", + }) + Expect(err).NotTo(HaveOccurred()) + + reconciler := &DpuNetworkReconciler{Client: mgr.GetClient(), Scheme: mgr.GetScheme()} + err = reconciler.SetupWithManager(mgr) + Expect(err).NotTo(HaveOccurred()) + + wg.Add(1) + go func() { + defer GinkgoRecover() + err := mgr.Start(ctx) + Expect(err).NotTo(HaveOccurred()) + wg.Done() + }() + + <-mgr.Elected() + return mgr +} + +var _ = Describe("DpuNetwork Controller", Ordered, func() { + var ( + cancel context.CancelFunc + ctx context.Context + wg sync.WaitGroup + restConfig *rest.Config + mgr ctrl.Manager + testCluster testutils.KindCluster + ) + + BeforeAll(func() { + opts := zap.Options{Development: true} + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + testCluster = testutils.KindCluster{Name: "dpu-operator-dpunetwork-test-cluster"} + restConfig = testCluster.EnsureExists() + ctx, cancel = context.WithCancel(context.Background()) + mgr = startDpuNetworkControllerManager(ctx, restConfig, &wg) + + // DpuNetwork controller writes into vars.Namespace, so make sure it exists. + ns := testutils.DpuOperatorNamespace() + testutils.CreateNamespace(mgr.GetClient(), ns) + }) + + AfterAll(func() { + cancel() + wg.Wait() + if os.Getenv("FAST_TEST") == "false" { + testCluster.EnsureDeleted() + } + }) + + AfterEach(func() { + if mgr == nil { + return + } + net := &configv1.DpuNetwork{} + err := mgr.GetClient().Get(context.Background(), types.NamespacedName{Name: "net1"}, net) + if err == nil { + Expect(mgr.GetClient().Delete(context.Background(), net)).To(Succeed()) + Eventually(func() error { + return mgr.GetClient().Get(context.Background(), types.NamespacedName{Name: "net1"}, net) + }, testutils.TestAPITimeout*5, testutils.TestRetryInterval).ShouldNot(Succeed()) + } + }) + + It("should create/update ConfigMap and NAD and set status", func() { + net := &configv1.DpuNetwork{ + ObjectMeta: metav1.ObjectMeta{Name: "net1"}, + Spec: configv1.DpuNetworkSpec{ + DpuSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "vfId", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"1-3", "5"}, + }}, + }, + }, + } + + Expect(mgr.GetClient().Create(context.Background(), net)).To(Succeed()) + + expectedResource := "openshift.io/dpunetwork-net1" + + By("Ensuring ConfigMap is written") + Eventually(func() (string, error) { + cm := &corev1.ConfigMap{} + err := mgr.GetClient().Get(context.Background(), types.NamespacedName{Name: "dpu-device-plugin-config", Namespace: vars.Namespace}, cm) + if err != nil { + return "", err + } + return cm.Data["config.json"], nil + }, testutils.TestAPITimeout*5, testutils.TestRetryInterval).ShouldNot(BeEmpty()) + + cm := &corev1.ConfigMap{} + Expect(mgr.GetClient().Get(context.Background(), types.NamespacedName{Name: "dpu-device-plugin-config", Namespace: vars.Namespace}, cm)).To(Succeed()) + cfg := testDevicePluginConfig{} + Expect(json.Unmarshal([]byte(cm.Data["config.json"]), &cfg)).To(Succeed()) + + found := false + for _, r := range cfg.Resources { + if r.DpuNetworkName == "net1" { + Expect(r.ResourceName).To(Equal(expectedResource)) + found = true + break + } + } + Expect(found).To(BeTrue()) + + By("Ensuring NAD is created") + Eventually(func() (map[string]string, error) { + nad := &netattdefv1.NetworkAttachmentDefinition{} + err := mgr.GetClient().Get(context.Background(), types.NamespacedName{Name: "net1-nad", Namespace: "default"}, nad) + if err != nil { + return nil, err + } + return nad.Annotations, nil + }, testutils.TestAPITimeout*5, testutils.TestRetryInterval).Should(HaveKeyWithValue("k8s.v1.cni.cncf.io/resourceName", expectedResource)) + + By("Ensuring status is set") + Eventually(func() string { + latest := &configv1.DpuNetwork{} + if err := mgr.GetClient().Get(context.Background(), types.NamespacedName{Name: "net1"}, latest); err != nil { + return "" + } + if !meta.IsStatusConditionTrue(latest.Status.Conditions, "Ready") { + return "" + } + return latest.Status.ResourceName + }, testutils.TestAPITimeout*5, testutils.TestRetryInterval).Should(Equal(expectedResource)) + }) +}) diff --git a/internal/daemon/device-plugin/deviceplugin.go b/internal/daemon/device-plugin/deviceplugin.go index 16b5bd5a0..48c29d125 100644 --- a/internal/daemon/device-plugin/deviceplugin.go +++ b/internal/daemon/device-plugin/deviceplugin.go @@ -2,11 +2,15 @@ package deviceplugin import ( "context" + "encoding/json" "fmt" "net" "os" "path/filepath" "reflect" + "sort" + "strconv" + "strings" "sync" "time" @@ -15,28 +19,42 @@ import ( dpudevicehandler "github.com/openshift/dpu-operator/internal/daemon/device-handler/dpu-device-handler" "github.com/openshift/dpu-operator/internal/daemon/plugin" "github.com/openshift/dpu-operator/internal/utils" + "github.com/openshift/dpu-operator/pkgs/vars" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" pluginapi "k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" ) const ( - DpuResourceName = "openshift.io/dpu" + DefaultDpuResourceName = "openshift.io/dpu" + AcceleratedResourceName = "openshift.io/dpu-accelerated" + + acceleratedDevicePrefix = "accelerated:" + + dpuDevicePluginConfigMapName = "dpu-device-plugin-config" + dpuDevicePluginConfigKey = "config.json" + configMapPollInterval = 30 * time.Second ) -// dpServer manages the k8s Device Plugin Server -type dpServer struct { - devices map[string]pluginapi.Device // for Kubelet DP API - grpcServer *grpc.Server - pluginapi.DevicePluginServer - log logr.Logger - pathManager utils.PathManager - deviceHandler dh.DeviceHandler - startedWg sync.WaitGroup - vsp plugin.VendorPlugin +// ConfigMap model +type devicePluginConfig struct { + Resources []devicePluginResource `json:"resources"` +} + +type devicePluginResource struct { + ResourceName string `json:"resourceName"` + DpuNetworkName string `json:"dpuNetworkName"` + VfRanges []string `json:"vfRanges,omitempty"` + IsAccelerated bool `json:"isAccelerated,omitempty"` } +// DevicePlugin interface — used by host/dpu side managers + type DevicePlugin interface { SetupDevices() error ListenAndServe() error @@ -45,16 +63,33 @@ type DevicePlugin interface { Stop() error } +// dpServer individual device plugin gRPC server +type dpServer struct { + devicesMu sync.RWMutex + devices map[string]pluginapi.Device + grpcServer *grpc.Server + pluginapi.DevicePluginServer + log logr.Logger + pathManager utils.PathManager + deviceHandler dh.DeviceHandler + startedWg sync.WaitGroup + resourceName string + drainCh chan struct{} + drainDone chan struct{} +} + func (dp *dpServer) sendDevices(stream pluginapi.DevicePlugin_ListAndWatchServer, devices *dh.DeviceList) error { resp := new(pluginapi.ListAndWatchResponse) for _, dev := range *devices { resp.Devices = append(resp.Devices, &dev) } - dp.log.Info("SendDevices:", "resp", resp) + dp.log.Info("SendDevices", "resp", resp) if err := stream.Send(resp); err != nil { dp.log.Error(err, "Cannot send devices to ListAndWatch server") - dp.grpcServer.Stop() + if dp.grpcServer != nil { + dp.grpcServer.Stop() + } return err } return nil @@ -75,6 +110,8 @@ func (dp *dpServer) devicesEqual(d1, d2 *dh.DeviceList) bool { } func (dp *dpServer) setDeviceCache(devices *dh.DeviceList) { + dp.devicesMu.Lock() + defer dp.devicesMu.Unlock() dp.devices = *devices for id, dev := range dp.devices { dp.log.Info("Cached device", "id", id, "dev.ID", dev.ID) @@ -82,6 +119,8 @@ func (dp *dpServer) setDeviceCache(devices *dh.DeviceList) { } func (dp *dpServer) checkCachedDeviceHealth(id string) (bool, error) { + dp.devicesMu.RLock() + defer dp.devicesMu.RUnlock() dev, ok := dp.devices[id] if !ok { return false, fmt.Errorf("invalid allocation request with non-existing device: %s", id) @@ -91,7 +130,23 @@ func (dp *dpServer) checkCachedDeviceHealth(id string) (bool, error) { func (dp *dpServer) ListAndWatch(empty *pluginapi.Empty, stream pluginapi.DevicePlugin_ListAndWatchServer) error { oldDevices := make(dh.DeviceList) + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + for { + select { + case <-dp.drainCh: + dp.log.Info("Drain requested; sending empty device list to kubelet", "resourceName", dp.resourceName) + emptyList := make(dh.DeviceList) + _ = dp.sendDevices(stream, &emptyList) + // Keep the stream open briefly so kubelet can process the + // empty device list before the gRPC connection is torn down. + time.Sleep(2 * time.Second) + close(dp.drainDone) + return nil + case <-ticker.C: + } + newDevices, err := dp.deviceHandler.GetDevices() if err != nil { dp.log.Error(err, "Failed to get Devices") @@ -106,18 +161,17 @@ func (dp *dpServer) ListAndWatch(empty *pluginapi.Empty, stream pluginapi.Device oldDevices = *newDevices dp.setDeviceCache(newDevices) } - time.Sleep(5 * time.Second) } } // Allocate passes the dev name as an env variable to the requesting container func (dp *dpServer) Allocate(ctx context.Context, rqt *pluginapi.AllocateRequest) (*pluginapi.AllocateResponse, error) { resp := new(pluginapi.AllocateResponse) - devName := "" for _, container := range rqt.ContainerRequests { containerResp := new(pluginapi.ContainerAllocateResponse) + devName := "" for _, id := range container.DevicesIDs { - dp.log.Info("DeviceID in Allocate:", "id", id) + dp.log.Info("DeviceID in Allocate", "id", id) isHealthy, err := dp.checkCachedDeviceHealth(id) if err != nil { return nil, err @@ -131,7 +185,7 @@ func (dp *dpServer) Allocate(ctx context.Context, rqt *pluginapi.AllocateRequest devName = devName + id + "," } - dp.log.Info("Device(s) allocated:", "devName", devName) + dp.log.Info("Device(s) allocated", "devName", devName) envmap := make(map[string]string) envmap["NF-DEV"] = devName @@ -149,10 +203,10 @@ func (dp *dpServer) Listen() (net.Listener, error) { return nil, fmt.Errorf("failed to cleanup Device Plugin server endpoint: %v", err) } - dp.log.Info("Starting Device Plugin server at:", "pluginEndpoint", pluginEndpoint) + dp.log.Info("Starting Device Plugin server at", "pluginEndpoint", pluginEndpoint) lis, err := net.Listen("unix", pluginEndpoint) if err != nil { - return nil, fmt.Errorf("resource %s failed to listen to Device Plugin server: %v", DpuResourceName, err) + return nil, fmt.Errorf("resource %s failed to listen to Device Plugin server: %v", dp.resourceName, err) } pluginapi.RegisterDevicePluginServer(dp.grpcServer, dp) @@ -230,9 +284,9 @@ func (dp *dpServer) ensureDevicePluginServerStarted() error { pluginEndpoint := dp.pathManager.PluginEndpoint() conn, err := dp.connectWithRetry("unix:" + pluginEndpoint) if err != nil { - return fmt.Errorf("resource %s unable to establish test connection with gRPC server: %v", DpuResourceName, err) + return fmt.Errorf("resource %s unable to establish test connection with gRPC server: %v", dp.resourceName, err) } - dp.log.Info("Device plugin endpoint started serving:", "DpuResourceName", DpuResourceName) + dp.log.Info("Device plugin endpoint started serving", "resourceName", dp.resourceName) conn.Close() return nil } @@ -241,7 +295,7 @@ func (dp *dpServer) registerWithKubelet() error { kubeletEndpoint := filepath.Join("unix:", dp.pathManager.KubeletEndPoint()) conn, err := grpc.Dial(kubeletEndpoint, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { - return fmt.Errorf("resource %s unable connect to Kubelet: %v", DpuResourceName, err) + return fmt.Errorf("resource %s unable connect to Kubelet: %v", dp.resourceName, err) } defer conn.Close() @@ -250,22 +304,18 @@ func (dp *dpServer) registerWithKubelet() error { request := &pluginapi.RegisterRequest{ Version: pluginapi.Version, Endpoint: dp.pathManager.PluginEndpointFilename(), - ResourceName: DpuResourceName, + ResourceName: dp.resourceName, } if _, err = client.Register(context.Background(), request); err != nil { - return fmt.Errorf("unable to register resource %s with Kubelet: %v", DpuResourceName, err) + return fmt.Errorf("unable to register resource %s with Kubelet: %v", dp.resourceName, err) } - dp.log.Info("Device plugin registered with Kubelet", "DpuResourceName", DpuResourceName) + dp.log.Info("Device plugin registered with Kubelet", "resourceName", dp.resourceName) return nil } -// connectWithRetry tries to establish a connection with the given endpoint, with retries. func (dp *dpServer) connectWithRetry(endpoint string) (*grpc.ClientConn, error) { - var conn *grpc.ClientConn - var err error - retryPolicy := `{ "methodConfig": [{ "waitForReady": true, @@ -281,7 +331,7 @@ func (dp *dpServer) connectWithRetry(endpoint string) (*grpc.ClientConn, error) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - conn, err = grpc.DialContext( + conn, err := grpc.DialContext( ctx, endpoint, grpc.WithTransportCredentials(insecure.NewCredentials()), @@ -297,11 +347,24 @@ func (dp *dpServer) connectWithRetry(endpoint string) (*grpc.ClientConn, error) } func (dp *dpServer) Stop() error { - dp.log.Info("Stopping Device Plugin...") + dp.log.Info("Stopping Device Plugin...", "resourceName", dp.resourceName) if dp.grpcServer == nil { return nil } + select { + case <-dp.drainCh: + // already drained + default: + close(dp.drainCh) + select { + case <-dp.drainDone: + dp.log.Info("Drain complete; kubelet received empty device list", "resourceName", dp.resourceName) + case <-time.After(5 * time.Second): + dp.log.Info("Drain timed out; stopping gRPC server anyway", "resourceName", dp.resourceName) + } + } + dp.grpcServer.Stop() dp.startedWg.Wait() dp.grpcServer = nil @@ -328,26 +391,536 @@ func (dp *dpServer) GetDevicePluginOptions(ctx context.Context, empty *pluginapi }, nil } -func WithPathManager(pathManager utils.PathManager) func(*dpServer) { - return func(d *dpServer) { - d.pathManager = pathManager +// filteredDeviceHandler — wraps a real DeviceHandler and filters its output. +// Two modes controlled by acceleratedOnly: +// - acceleratedOnly=false: returns VF devices (excluding accelerated:-prefixed +// ones) filtered by positional index from allowedVFs. +// - acceleratedOnly=true: returns only accelerated:-prefixed devices +// (with the prefix stripped so the real interface name is advertised). +type filteredDeviceHandler struct { + inner dh.DeviceHandler + allowedVFs map[int32]struct{} + acceleratedOnly bool + dpuMode bool +} + +func newFilteredDeviceHandler(inner dh.DeviceHandler, vfIDs []int32, dpuMode bool) *filteredDeviceHandler { + allowed := make(map[int32]struct{}, len(vfIDs)) + for _, id := range vfIDs { + allowed[id] = struct{}{} } + return &filteredDeviceHandler{inner: inner, allowedVFs: allowed, dpuMode: dpuMode} } -func NewDevicePlugin(vsp plugin.VendorPlugin, dpuMode bool, pm utils.PathManager, opts ...func(*dpServer)) *dpServer { - dh := dpudevicehandler.NewDpuDeviceHandler(vsp, dpudevicehandler.WithDpuMode(dpuMode), dpudevicehandler.WithPathManager(pm)) - dp := &dpServer{ +func newAcceleratedDeviceHandler(inner dh.DeviceHandler) *filteredDeviceHandler { + return &filteredDeviceHandler{inner: inner, acceleratedOnly: true} +} + +func (h *filteredDeviceHandler) SetupDevices() error { + return nil +} + +func (h *filteredDeviceHandler) GetDevices() (*dh.DeviceList, error) { + all, err := h.inner.GetDevices() + if err != nil { + return nil, err + } + + filtered := make(dh.DeviceList) + + if h.acceleratedOnly { + for id, dev := range *all { + if strings.HasPrefix(id, acceleratedDevicePrefix) { + realName := strings.TrimPrefix(id, acceleratedDevicePrefix) + dev.ID = realName + filtered[realName] = dev + } + } + return &filtered, nil + } + + // VF mode: collect non-accelerated devices and try stable VF-index mapping. + var nonAccel []string + for id := range *all { + if !strings.HasPrefix(id, acceleratedDevicePrefix) { + nonAccel = append(nonAccel, id) + } + } + sort.Strings(nonAccel) + + // Try suffix-based mapping first (stable when VFs move into pod namespaces). + // If no device names have a recognisable VF suffix, fall back to positional. + useSuffix := false + for _, id := range nonAccel { + if extractVFIndex(id) >= 0 { + useSuffix = true + break + } + } + + if useSuffix { + for _, id := range nonAccel { + vfID := extractVFIndex(id) + if vfID < 0 { + continue + } + if _, ok := h.allowedVFs[int32(vfID)]; ok { + filtered[id] = (*all)[id] + } + } + } else { + for idx, id := range nonAccel { + if _, ok := h.allowedVFs[int32(idx)]; ok { + filtered[id] = (*all)[id] + } + } + } + return &filtered, nil +} + +// extractVFIndex parses a VF interface name like "enP2p1s0v5" and returns a +// 0-based VF index (suffix - 1, because v0 is the management interface). +// Returns -1 if the name doesn't match the expected pattern. +func extractVFIndex(name string) int { + idx := strings.LastIndex(name, "v") + if idx < 0 || idx == len(name)-1 { + return -1 + } + n, err := strconv.Atoi(name[idx+1:]) + if err != nil || n <= 0 { + return -1 + } + return n - 1 +} + +// VF range helpers +func expandAllRanges(vfRanges []string) []int32 { + set := map[int32]struct{}{} + for _, r := range vfRanges { + for _, id := range expandRange(r) { + set[id] = struct{}{} + } + } + out := make([]int32, 0, len(set)) + for id := range set { + out = append(out, id) + } + sort.Slice(out, func(i, j int) bool { return out[i] < out[j] }) + return out +} + +func expandRange(s string) []int32 { + s = strings.TrimSpace(s) + if !strings.Contains(s, "-") { + v, err := strconv.Atoi(s) + if err != nil { + return nil + } + return []int32{int32(v)} + } + parts := strings.SplitN(s, "-", 2) + if len(parts) != 2 { + return nil + } + start, err1 := strconv.Atoi(strings.TrimSpace(parts[0])) + end, err2 := strconv.Atoi(strings.TrimSpace(parts[1])) + if err1 != nil || err2 != nil { + return nil + } + if end < start { + start, end = end, start + } + out := make([]int32, 0, end-start+1) + for i := start; i <= end; i++ { + out = append(out, int32(i)) + } + return out +} + +// DevicePluginManager +// When no ConfigMap exists (or it has zero resources): +// - Runs the default openshift.io/dpu plugin using VSP-discovered devices. +// +// When the ConfigMap has resources: +// - Does NOT start the default plugin. +// - Starts one dpServer per ConfigMap resource entry, each with its own +// Unix socket, resource name, and filtered view of real VSP devices. +// - On DPU side with isAccelerated=true, also starts an accelerated device +// plugin for the accelerated:-prefixed devices from the VSP. +// - Polls the ConfigMap every 30s and adds/removes plugins as needed. + +type DevicePluginManager struct { + log logr.Logger + k8sClient client.Client + vsp plugin.VendorPlugin + dpuMode bool + pathManager utils.PathManager + + defaultPlugin *dpServer + networkPlugins map[string]*dpServer + networkConfigs map[string]devicePluginResource + acceleratedPlugin *dpServer + lastAcceleratedMode *bool + mu sync.Mutex + cancelWatch context.CancelFunc + defaultPluginStarted bool +} + +func NewDevicePluginManager(vsp plugin.VendorPlugin, dpuMode bool, pm utils.PathManager, k8sClient client.Client) *DevicePluginManager { + defaultDH := dpudevicehandler.NewDpuDeviceHandler(vsp, dpudevicehandler.WithDpuMode(dpuMode), dpudevicehandler.WithPathManager(pm)) + defaultPlugin := &dpServer{ devices: make(map[string]pluginapi.Device), grpcServer: grpc.NewServer(), log: ctrl.Log.WithName("DevicePlugin"), pathManager: pm, - deviceHandler: dh, - vsp: vsp, + deviceHandler: defaultDH, + resourceName: DefaultDpuResourceName, + drainCh: make(chan struct{}), + drainDone: make(chan struct{}), + } + + return &DevicePluginManager{ + log: ctrl.Log.WithName("DevicePluginManager"), + k8sClient: k8sClient, + vsp: vsp, + dpuMode: dpuMode, + pathManager: pm, + defaultPlugin: defaultPlugin, + networkPlugins: make(map[string]*dpServer), + networkConfigs: make(map[string]devicePluginResource), + } +} + +func (m *DevicePluginManager) SetupDevices() error { + return m.defaultPlugin.SetupDevices() +} + +func (m *DevicePluginManager) Listen() (net.Listener, error) { + return m.defaultPlugin.Listen() +} + +func (m *DevicePluginManager) Serve(lis net.Listener) error { + watchCtx, cancel := context.WithCancel(context.Background()) + m.cancelWatch = cancel + + cfg := m.readConfig() + hasConfigResources := cfg != nil && len(cfg.Resources) > 0 + + if hasConfigResources { + m.log.Info("ConfigMap has resources; starting per-network plugins only (no default plugin)") + m.syncNetworkPlugins() + go m.watchConfigMap(watchCtx) + <-watchCtx.Done() + return nil } - for _, opt := range opts { - opt(dp) + m.log.Info("No ConfigMap resources found; starting default plugin", "resource", DefaultDpuResourceName) + m.mu.Lock() + m.defaultPluginStarted = true + m.mu.Unlock() + go m.watchConfigMap(watchCtx) + return m.defaultPlugin.Serve(lis) +} + +func (m *DevicePluginManager) ListenAndServe() error { + lis, err := m.Listen() + if err != nil { + return err + } + return m.Serve(lis) +} + +func (m *DevicePluginManager) Stop() error { + if m.cancelWatch != nil { + m.cancelWatch() } - return dp + m.mu.Lock() + plugins := make([]*dpServer, 0, len(m.networkPlugins)) + for _, dp := range m.networkPlugins { + plugins = append(plugins, dp) + } + m.networkPlugins = make(map[string]*dpServer) + m.networkConfigs = make(map[string]devicePluginResource) + + accelPlugin := m.acceleratedPlugin + m.acceleratedPlugin = nil + + stopDefault := m.defaultPluginStarted + m.defaultPluginStarted = false + m.mu.Unlock() + + for _, dp := range plugins { + if err := dp.Stop(); err != nil { + m.log.Error(err, "Failed to stop per-network device plugin", "resource", dp.resourceName) + } + } + if accelPlugin != nil { + m.log.Info("Stopping accelerated device plugin") + if err := accelPlugin.Stop(); err != nil { + m.log.Error(err, "Failed to stop accelerated device plugin") + } + } + if stopDefault { + return m.defaultPlugin.Stop() + } + return nil +} + +func (m *DevicePluginManager) watchConfigMap(ctx context.Context) { + ticker := time.NewTicker(configMapPollInterval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + m.syncNetworkPlugins() + } + } +} + +func (m *DevicePluginManager) readConfig() *devicePluginConfig { + if m.k8sClient == nil { + return nil + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + cm := &corev1.ConfigMap{} + key := types.NamespacedName{Name: dpuDevicePluginConfigMapName, Namespace: vars.Namespace} + if err := m.k8sClient.Get(ctx, key, cm); err != nil { + if !apierrors.IsNotFound(err) { + m.log.Error(err, "Failed to read device plugin ConfigMap") + } + return nil + } + + raw := "" + if cm.Data != nil { + raw = cm.Data[dpuDevicePluginConfigKey] + } + if strings.TrimSpace(raw) == "" { + return nil + } + + var cfg devicePluginConfig + if err := json.Unmarshal([]byte(raw), &cfg); err != nil { + m.log.Error(err, "Failed to parse device plugin config.json") + return nil + } + return &cfg +} + +// syncNetworkPlugins Entry point for reconciling per-network plugins +func (m *DevicePluginManager) syncNetworkPlugins() { + cfg := m.readConfig() + + toStop, stopDefault := m.reconcilePlugins(cfg) + + for _, dp := range toStop { + if err := dp.Stop(); err != nil { + m.log.Error(err, "Failed to stop per-network device plugin", "resource", dp.resourceName) + } + } + if stopDefault { + if err := m.defaultPlugin.Stop(); err != nil { + m.log.Error(err, "Failed to stop default plugin") + } + } +} + +// reconcilePlugins holds the lock, updates maps, starts new plugins, and +// returns the list of plugins that need to be stopped (outside the lock). +func (m *DevicePluginManager) reconcilePlugins(cfg *devicePluginConfig) (toStop []*dpServer, stopDefault bool) { + m.mu.Lock() + defer m.mu.Unlock() + + desired := make(map[string]devicePluginResource) + if cfg != nil { + for _, r := range cfg.Resources { + if r.DpuNetworkName != "" && r.ResourceName != "" { + desired[r.DpuNetworkName] = r + } + } + } + + // Remove plugins for networks no longer in the ConfigMap, or whose + // config has changed (vfRanges, isAccelerated). Changed plugins will + // be re-created in the loop below. + for name, dp := range m.networkPlugins { + newRes, stillDesired := desired[name] + if !stillDesired || m.configChanged(name, newRes) { + m.log.Info("Removing per-network device plugin", "network", name, "resource", dp.resourceName, "reason", m.removeReason(stillDesired)) + toStop = append(toStop, dp) + delete(m.networkPlugins, name) + delete(m.networkConfigs, name) + } + } + + // Start plugins for new networks (including ones just removed due to config change). + pluginsStarted := len(m.networkPlugins) > 0 + needsAccelerated := false + + for name, res := range desired { + // On DPU side, non-accelerated networks are handled by the default plugin. + if m.dpuMode && !res.IsAccelerated { + continue + } + + if res.IsAccelerated { + needsAccelerated = true + } + + if _, exists := m.networkPlugins[name]; exists { + continue + } + + if started := m.startPlugin(name, res); started { + pluginsStarted = true + } + } + + // DPU-only: manage accelerated device plugin and VSP mode. + if m.dpuMode { + m.syncAcceleratedPlugin(needsAccelerated) + if needsAccelerated { + pluginsStarted = true + } + } + + if pluginsStarted && m.defaultPluginStarted { + m.log.Info("Per-network plugins running; stopping default plugin") + stopDefault = true + m.defaultPluginStarted = false + } + + if !pluginsStarted && !m.defaultPluginStarted { + m.log.Info("No per-network plugins active; restarting default plugin", "resource", DefaultDpuResourceName) + m.defaultPluginStarted = true + m.defaultPlugin.grpcServer = grpc.NewServer() + m.defaultPlugin.drainCh = make(chan struct{}) + m.defaultPlugin.drainDone = make(chan struct{}) + go func() { + lis, err := m.defaultPlugin.Listen() + if err != nil { + m.log.Error(err, "Failed to listen for default plugin restart") + m.mu.Lock() + m.defaultPluginStarted = false + m.mu.Unlock() + return + } + if err := m.defaultPlugin.Serve(lis); err != nil { + m.log.Error(err, "Default plugin serve failed after restart") + m.mu.Lock() + m.defaultPluginStarted = false + m.mu.Unlock() + } + }() + } + + return toStop, stopDefault +} + +// startPlugin creates and starts a per-network device plugin for the given +// DpuNetwork resource. Returns true if the plugin was started. +func (m *DevicePluginManager) startPlugin(name string, res devicePluginResource) bool { + vfIDs := expandAllRanges(res.VfRanges) + if len(vfIDs) == 0 { + m.log.Info("Skipping per-network device plugin with no VF IDs", "network", name) + return false + } + + networkPM := m.pathManager.PathManagerFor(name) + dp := &dpServer{ + devices: make(map[string]pluginapi.Device), + grpcServer: grpc.NewServer(), + log: ctrl.Log.WithName("DevicePlugin").WithValues("network", name), + pathManager: networkPM, + deviceHandler: newFilteredDeviceHandler(m.defaultPlugin.deviceHandler, vfIDs, m.dpuMode), + resourceName: res.ResourceName, + drainCh: make(chan struct{}), + drainDone: make(chan struct{}), + } + + m.networkPlugins[name] = dp + m.networkConfigs[name] = res + m.log.Info("Starting per-network device plugin", "network", name, "resource", res.ResourceName, "vfIDs", vfIDs, "endpoint", networkPM.PluginEndpoint()) + + go func() { + if err := dp.ListenAndServe(); err != nil { + m.log.Error(err, "Per-network device plugin failed", "network", name) + } + }() + + return true +} + +// syncAcceleratedPlugin starts or stops the accelerated device plugin and +// updates the VSP's accelerated mode accordingly. +func (m *DevicePluginManager) syncAcceleratedPlugin(needsAccelerated bool) { + if m.lastAcceleratedMode == nil || *m.lastAcceleratedMode != needsAccelerated { + if err := m.vsp.SetDpuNetworkConfig(needsAccelerated); err != nil { + m.log.Error(err, "Failed to set accelerated mode on VSP; will retry next cycle", "isAccelerated", needsAccelerated) + } else { + m.lastAcceleratedMode = &needsAccelerated + } + } + + if needsAccelerated && m.acceleratedPlugin == nil { + acceleratedPM := m.pathManager.PathManagerFor("accelerated") + m.acceleratedPlugin = &dpServer{ + devices: make(map[string]pluginapi.Device), + grpcServer: grpc.NewServer(), + log: ctrl.Log.WithName("DevicePlugin").WithValues("resource", AcceleratedResourceName), + pathManager: acceleratedPM, + deviceHandler: newAcceleratedDeviceHandler(m.defaultPlugin.deviceHandler), + resourceName: AcceleratedResourceName, + drainCh: make(chan struct{}), + drainDone: make(chan struct{}), + } + m.log.Info("Starting accelerated device plugin", "resource", AcceleratedResourceName, "endpoint", acceleratedPM.PluginEndpoint()) + + go func(dp *dpServer) { + if err := dp.ListenAndServe(); err != nil { + m.log.Error(err, "Accelerated device plugin failed") + } + }(m.acceleratedPlugin) + } + + if !needsAccelerated && m.acceleratedPlugin != nil { + m.log.Info("No accelerated networks; stopping accelerated device plugin") + m.acceleratedPlugin.Stop() + m.acceleratedPlugin = nil + } +} + +func (m *DevicePluginManager) configChanged(name string, newRes devicePluginResource) bool { + oldRes, ok := m.networkConfigs[name] + if !ok { + return true + } + return oldRes.ResourceName != newRes.ResourceName || + oldRes.IsAccelerated != newRes.IsAccelerated || + !vfRangesEqual(oldRes.VfRanges, newRes.VfRanges) +} + +func (m *DevicePluginManager) removeReason(stillDesired bool) string { + if !stillDesired { + return "removed from ConfigMap" + } + return "config changed" +} + +func vfRangesEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true } diff --git a/internal/daemon/dpusidemanager.go b/internal/daemon/dpusidemanager.go index f34448a8d..228008dbb 100644 --- a/internal/daemon/dpusidemanager.go +++ b/internal/daemon/dpusidemanager.go @@ -21,11 +21,13 @@ import ( "github.com/openshift/dpu-operator/pkgs/vars" pb "github.com/opiproject/opi-api/network/evpn-gw/v1alpha1/gen/go" lifecycleapi "github.com/opiproject/opi-api/v1/gen/go/lifecycle/v1alpha1" + "github.com/vishvananda/netlink" "google.golang.org/grpc" emptypb "google.golang.org/protobuf/types/known/emptypb" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/metrics/filters" "sigs.k8s.io/controller-runtime/pkg/metrics/server" ) @@ -113,7 +115,12 @@ func NewDpuSideManager(vsp plugin.VendorPlugin, config *rest.Config, opts ...fun opt(d) } - d.dp = deviceplugin.NewDevicePlugin(vsp, true, d.pathManager) + if k8sClient, err := client.New(d.config, client.Options{Scheme: scheme.Scheme}); err != nil { + d.log.Error(err, "Failed to create Kubernetes client for device plugin; falling back to default-only registration") + d.dp = deviceplugin.NewDevicePluginManager(vsp, true, d.pathManager, nil) + } else { + d.dp = deviceplugin.NewDevicePluginManager(vsp, true, d.pathManager, k8sClient) + } return d, nil } @@ -149,12 +156,21 @@ func (d *DpuSideManager) cniCmdNfAddHandler(req *cnitypes.PodRequest) (*cni100.R return nil, fmt.Errorf("SRIOV manager failed in add handler: %v", err) } - d.macStore[req.Netns] = append(d.macStore[req.Netns], req.CNIConf.MAC) - if len(d.macStore[req.Netns]) == 2 { - d.log.Info("cniCmdNfAddHandler", "req.Netns", req.Netns) - macs := d.macStore[req.Netns] - d.vsp.CreateNetworkFunction(macs[0], macs[1]) + bridgeID := req.CNIConf.BridgeID + + if req.CNIConf.IsAccelerated { + d.log.Info("cniCmdNfAddHandler accelerated mode: calling CNF per VF", "mac", req.CNIConf.MAC, "bridgeID", bridgeID) + d.macStore[req.Netns] = append(d.macStore[req.Netns], req.CNIConf.MAC) + d.vsp.CreateNetworkFunction(req.CNIConf.MAC, "", bridgeID) + } else { + d.macStore[req.Netns] = append(d.macStore[req.Netns], req.CNIConf.MAC) + if len(d.macStore[req.Netns]) == 2 { + d.log.Info("cniCmdNfAddHandler", "req.Netns", req.Netns, "bridgeID", bridgeID) + macs := d.macStore[req.Netns] + d.vsp.CreateNetworkFunction(macs[0], macs[1], bridgeID) + } } + d.log.Info("cniCmdNfAddHandler CmdAdd succeeded") return res, nil } @@ -166,19 +182,71 @@ func (d *DpuSideManager) cniCmdNfDelHandler(req *cnitypes.PodRequest) (*cni100.R return nil, errors.New("SRIOV manager failed in del handler") } - macs := d.macStore[req.Netns] + bridgeID := req.CNIConf.BridgeID - if len(macs) == 2 { - d.log.Info("cniCmdNfDelHandler", "req.Netns", req.Netns) - d.vsp.DeleteNetworkFunction(macs[0], macs[1]) + if req.CNIConf.IsAccelerated { + macs := d.macStore[req.Netns] + mac := "" + if len(macs) > 0 { + mac = macs[len(macs)-1] + d.macStore[req.Netns] = macs[:len(macs)-1] + } + d.log.Info("cniCmdNfDelHandler accelerated mode: calling DNF per VF", "mac", mac, "bridgeID", bridgeID) + d.vsp.DeleteNetworkFunction(mac, "", bridgeID) + } else { + macs := d.macStore[req.Netns] + if len(macs) == 2 { + d.log.Info("cniCmdNfDelHandler", "req.Netns", req.Netns, "bridgeID", bridgeID) + d.vsp.DeleteNetworkFunction(macs[0], macs[1], bridgeID) + } + if len(macs) > 0 { + d.macStore[req.Netns] = macs[:len(macs)-1] + } } - d.macStore[req.Netns] = macs[:len(macs)-1] - d.log.Info("cniCmdNfDelHandler CmdDel succeeded") return nil, nil } +// releaseNfDevices recovers any devices (VF representors or veths) that are +// still inside NF pod namespaces during graceful shutdown. This must run +// before the CNI server and VSP are stopped so the host can safely reset +// sriov_numvfs without hitting a kernel D-state hang. +func (d *DpuSideManager) releaseNfDevices() { + d.log.Info("releaseNfDevices: checking for devices still in pod namespaces") + + devices, err := d.vsp.GetDevices() + if err != nil { + d.log.Error(err, "releaseNfDevices: failed to get device list from VSP") + return + } + + for _, dev := range devices.Devices { + devName := dev.ID + if _, err := netlink.LinkByName(devName); err == nil { + continue + } + + links, listErr := netlink.LinkList() + if listErr != nil { + d.log.Error(listErr, "releaseNfDevices: failed to list links") + return + } + for _, link := range links { + if link.Attrs().Alias == devName { + d.log.Info("releaseNfDevices: found device under temp name, restoring", + "tempName", link.Attrs().Name, "originalName", devName) + if err := netlink.LinkSetName(link, devName); err != nil { + d.log.Error(err, "releaseNfDevices: failed to rename", "device", devName) + } else if err := netlink.LinkSetUp(link); err != nil { + d.log.Error(err, "releaseNfDevices: failed to bring up", "device", devName) + } + break + } + } + } +} + func (d *DpuSideManager) Listen() (net.Listener, error) { d.startedWg.Add(1) d.log.Info("Starting DpuDaemon") @@ -225,6 +293,7 @@ func (d *DpuSideManager) Serve(ctx context.Context, listener net.Listener) error go func() { <-ctx.Done() d.log.Info("Context cancelled, shutting down servers") + d.releaseNfDevices() d.server.Stop() d.dp.Stop() d.vsp.Close() diff --git a/internal/daemon/dpusidemanager_test.go b/internal/daemon/dpusidemanager_test.go index 6b170ca99..934f17e7f 100644 --- a/internal/daemon/dpusidemanager_test.go +++ b/internal/daemon/dpusidemanager_test.go @@ -36,7 +36,7 @@ func waitAllNodesDpuAllocatable(client client.Client) { } readyNodes := 0 for _, node := range latestNodes.Items { - allocatableQuantity, ok := node.Status.Allocatable[deviceplugin.DpuResourceName] + allocatableQuantity, ok := node.Status.Allocatable[deviceplugin.DefaultDpuResourceName] if ok { allocatable, _ := allocatableQuantity.AsInt64() if allocatable > 0 { diff --git a/internal/daemon/hostsidemanager.go b/internal/daemon/hostsidemanager.go index 1a890a992..c0d544540 100644 --- a/internal/daemon/hostsidemanager.go +++ b/internal/daemon/hostsidemanager.go @@ -26,6 +26,7 @@ import ( "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" ) type HostSideManager struct { @@ -51,7 +52,7 @@ type HostSideManager struct { dpListener net.Listener } -func (d *HostSideManager) CreateBridgePort(pf int, vf int, vlan int, mac string) (*pb.BridgePort, error) { +func (d *HostSideManager) CreateBridgePort(pf int, vf int, vlan int, mac string, bridgeID string) (*pb.BridgePort, error) { err := d.connectWithRetry() if err != nil { return nil, fmt.Errorf("Failed to connect with retry: %v", err) @@ -69,8 +70,7 @@ func (d *HostSideManager) CreateBridgePort(pf int, vf int, vlan int, mac string) Ptype: 1, MacAddress: m, LogicalBridges: []string{ - // TODO: Remove +2 - fmt.Sprintf("%d", vf+2), + bridgeID, }, }, }, @@ -100,10 +100,16 @@ func NewHostSideManager(vsp plugin.VendorPlugin, opts ...func(*HostSideManager)) opt(h) } - h.dp = deviceplugin.NewDevicePlugin(vsp, false, h.pathManager) if h.config == nil { h.config = ctrl.GetConfigOrDie() } + + if k8sClient, err := client.New(h.config, client.Options{Scheme: scheme.Scheme}); err != nil { + h.log.Error(err, "Failed to create Kubernetes client for device plugin; falling back to default-only registration") + h.dp = deviceplugin.NewDevicePluginManager(vsp, false, h.pathManager, nil) + } else { + h.dp = deviceplugin.NewDevicePluginManager(vsp, false, h.pathManager, k8sClient) + } return h, nil } @@ -193,11 +199,12 @@ func (d *HostSideManager) cniCmdAddHandler(req *cnitypes.PodRequest) (*cni100.Re pf := 0 vf := req.CNIConf.VFID mac := req.CNIConf.OrigVfState.EffectiveMAC + bridgeID := req.CNIConf.BridgeID d.log.Info("addHandler", "CNIConf", req.CNIConf) // TODO: fix setting Vlan based on network definition in CR vlan := 2 // *req.CNIConf.Vlan - d.log.Info("addHandler", "pf", pf, "vf", vf, "mac", mac, "vlan", vlan) - _, err = d.CreateBridgePort(pf, vf, vlan, mac) + d.log.Info("addHandler", "pf", pf, "vf", vf, "mac", mac, "vlan", vlan, "bridgeID", bridgeID) + _, err = d.CreateBridgePort(pf, vf, vlan, mac, bridgeID) if err != nil { return nil, fmt.Errorf("Failed to call CreateBridgePort: %v", err) } diff --git a/internal/daemon/hostsidemanager_test.go b/internal/daemon/hostsidemanager_test.go index 4033da8f1..62b6bf0e0 100644 --- a/internal/daemon/hostsidemanager_test.go +++ b/internal/daemon/hostsidemanager_test.go @@ -63,11 +63,15 @@ func (v *DummyPlugin) DeleteBridgePort(deleteRequest *opi.DeleteBridgePortReques return nil } -func (g *DummyPlugin) CreateNetworkFunction(input string, output string) error { +func (g *DummyPlugin) CreateNetworkFunction(input string, output string, bridgeID string) error { return nil } -func (g *DummyPlugin) DeleteNetworkFunction(input string, output string) error { +func (g *DummyPlugin) DeleteNetworkFunction(input string, output string, bridgeID string) error { + return nil +} + +func (g *DummyPlugin) SetDpuNetworkConfig(isAccelerated bool) error { return nil } diff --git a/internal/daemon/plugin/vendorplugin.go b/internal/daemon/plugin/vendorplugin.go index 268c15ddc..b866419be 100644 --- a/internal/daemon/plugin/vendorplugin.go +++ b/internal/daemon/plugin/vendorplugin.go @@ -29,25 +29,27 @@ type VendorPlugin interface { Close() CreateBridgePort(bpr *opi.CreateBridgePortRequest) (*opi.BridgePort, error) DeleteBridgePort(bpr *opi.DeleteBridgePortRequest) error - CreateNetworkFunction(input string, output string) error - DeleteNetworkFunction(input string, output string) error + CreateNetworkFunction(input string, output string, bridgeID string) error + DeleteNetworkFunction(input string, output string, bridgeID string) error GetDevices() (*pb.DeviceListResponse, error) SetNumVfs(vfCount int32) (*pb.VfCount, error) + SetDpuNetworkConfig(isAccelerated bool) error } type GrpcPlugin struct { - log logr.Logger - client pb.LifeCycleServiceClient - k8sClient client.Client - opiClient opi.BridgePortServiceClient - nfclient nfapi.NetworkFunctionServiceClient - dsClient pb.DeviceServiceClient - dpuMode bool - dpuIdentifier DpuIdentifier - conn *grpc.ClientConn - pathManager utils.PathManager - initialized bool - initMutex sync.RWMutex + log logr.Logger + client pb.LifeCycleServiceClient + k8sClient client.Client + opiClient opi.BridgePortServiceClient + nfclient nfapi.NetworkFunctionServiceClient + dsClient pb.DeviceServiceClient + dpuNetworkConfigClient nfapi.DpuNetworkConfigServiceClient + dpuMode bool + dpuIdentifier DpuIdentifier + conn *grpc.ClientConn + pathManager utils.PathManager + initialized bool + initMutex sync.RWMutex } func (g *GrpcPlugin) Start(ctx context.Context) (string, int32, error) { @@ -103,6 +105,7 @@ func (g *GrpcPlugin) Close() { g.nfclient = nil g.opiClient = nil g.dsClient = nil + g.dpuNetworkConfigClient = nil } } @@ -151,6 +154,7 @@ func (g *GrpcPlugin) ensureConnected() error { g.nfclient = nfapi.NewNetworkFunctionServiceClient(conn) g.opiClient = opi.NewBridgePortServiceClient(conn) g.dsClient = pb.NewDeviceServiceClient(conn) + g.dpuNetworkConfigClient = nfapi.NewDpuNetworkConfigServiceClient(conn) return nil } @@ -171,24 +175,24 @@ func (g *GrpcPlugin) DeleteBridgePort(deleteRequest *opi.DeleteBridgePortRequest return err } -func (g *GrpcPlugin) CreateNetworkFunction(input string, output string) error { - g.log.Info("CreateNetworkFunction", "input", input, "output", output) +func (g *GrpcPlugin) CreateNetworkFunction(input string, output string, bridgeID string) error { + g.log.Info("CreateNetworkFunction", "input", input, "output", output, "bridgeID", bridgeID) err := g.ensureConnected() if err != nil { return fmt.Errorf("CreateNetworkFunction failed to ensure GRPC connection: %v", err) } - req := nfapi.NFRequest{Input: input, Output: output} + req := nfapi.NFRequest{Input: input, Output: output, BridgeId: bridgeID} _, err = g.nfclient.CreateNetworkFunction(context.TODO(), &req) return err } -func (g *GrpcPlugin) DeleteNetworkFunction(input string, output string) error { - g.log.Info("DeleteNetworkFunction", "input", input, "output", output) +func (g *GrpcPlugin) DeleteNetworkFunction(input string, output string, bridgeID string) error { + g.log.Info("DeleteNetworkFunction", "input", input, "output", output, "bridgeID", bridgeID) err := g.ensureConnected() if err != nil { return fmt.Errorf("DeleteNetworkFunction failed to ensure GRPC connection: %v", err) } - req := nfapi.NFRequest{Input: input, Output: output} + req := nfapi.NFRequest{Input: input, Output: output, BridgeId: bridgeID} _, err = g.nfclient.DeleteNetworkFunction(context.TODO(), &req) return err } @@ -212,6 +216,18 @@ func (g *GrpcPlugin) SetNumVfs(count int32) (*pb.VfCount, error) { return g.dsClient.SetNumVfs(context.Background(), c) } +func (g *GrpcPlugin) SetDpuNetworkConfig(isAccelerated bool) error { + err := g.ensureConnected() + if err != nil { + return fmt.Errorf("SetDpuNetworkConfig failed to ensure GRPC connection: %v", err) + } + req := &nfapi.DpuNetworkConfigRequest{ + IsAccelerated: isAccelerated, + } + _, err = g.dpuNetworkConfigClient.SetDpuNetworkConfig(context.TODO(), req) + return err +} + // IsInitialized returns true if the VSP has been successfully initialized func (g *GrpcPlugin) IsInitialized() bool { g.initMutex.RLock() diff --git a/internal/daemon/vendor-specific-plugins/marvell/main.go b/internal/daemon/vendor-specific-plugins/marvell/main.go index a18b3fea1..e2311e3f0 100644 --- a/internal/daemon/vendor-specific-plugins/marvell/main.go +++ b/internal/daemon/vendor-specific-plugins/marvell/main.go @@ -83,6 +83,7 @@ type mrvlNfPortMap struct { type mrvlVspServer struct { pb.UnimplementedLifeCycleServiceServer nfapi.UnimplementedNetworkFunctionServiceServer + nfapi.UnimplementedDpuNetworkConfigServiceServer pb.UnimplementedDeviceServiceServer opi.UnimplementedBridgePortServiceServer log logr.Logger @@ -94,6 +95,7 @@ type mrvlVspServer struct { pathManager utils.PathManager version string isDPUMode bool + isAccelerated bool deviceStore map[string]mrvlDeviceInfo noOfPortPairs int portType string @@ -328,6 +330,13 @@ func (vsp *mrvlVspServer) Init(ctx context.Context, in *pb.InitRequest) (*pb.IpP return result, err } +func (vsp *mrvlVspServer) SetDpuNetworkConfig(ctx context.Context, in *nfapi.DpuNetworkConfigRequest) (*nfapi.Empty, error) { + klog.Infof("Received SetDpuNetworkConfig() request: IsAccelerated: %v", in.IsAccelerated) + vsp.isAccelerated = in.IsAccelerated + klog.Infof("SetDpuNetworkConfig() done: isAccelerated set to %v", vsp.isAccelerated) + return &nfapi.Empty{}, nil +} + // getVFName function to get the VF Name of the given BridgePortName on DPU func (vsp *mrvlVspServer) getVFDetails(BridgePortName string) (string, string, error) { // regexp to get VFId from BridgePortName ex: host1-0 , vfId=0 @@ -373,6 +382,28 @@ func (vsp *mrvlVspServer) getVFDetails(BridgePortName string) (string, string, e func (vsp *mrvlVspServer) CreateBridgePort(ctx context.Context, in *opi.CreateBridgePortRequest) (*opi.BridgePort, error) { klog.Infof("Received CreateBridgePort() request: BridgePortId: %v, BridgePort: %v", in.BridgePortId, in.BridgePort) portName := in.BridgePort.Name + + nfKey := NfName // Default NF Name is "mrvl-nf1" + if len(in.BridgePort.Spec.LogicalBridges) > 0 && in.BridgePort.Spec.LogicalBridges[0] != "" { + nfKey = in.BridgePort.Spec.LogicalBridges[0] + } + klog.Infof("CreateBridgePort using nfKey: %v", nfKey) + // VSP in Accelerated mode + if vsp.isAccelerated { + if _, exists := vsp.networkStore[nfKey]; !exists { + klog.Errorf("Accelerated mode: NF not created yet for bridgeID %s, CBP rejected", nfKey) + return nil, fmt.Errorf("accelerated mode: NF not created yet for bridgeID %s, CreateBridgePort rejected", nfKey) + } + // TODO: Check if the port is already in the networkstore + klog.Infof("Accelerated mode: NF verified for bridgeID %s — CBP accepted", nfKey) + out := &opi.BridgePort{ + Name: fmt.Sprintf("bridge_port/%s", portName), + Spec: in.BridgePort.Spec, + Status: &opi.BridgePortStatus{}, + } + return out, nil + } + vfName, vfPCIAddress, err := vsp.getVFDetails(portName) if err != nil { klog.Errorf("Error occurred in getting VF Name: %v, BridgePortName: %v", err, portName) @@ -384,16 +415,16 @@ func (vsp *mrvlVspServer) CreateBridgePort(ctx context.Context, in *opi.CreateBr } klog.Info("Port Added to Bridge Successfully") // Store port into networkstore - if network, exists := vsp.networkStore[NfName]; exists { + if network, exists := vsp.networkStore[nfKey]; exists { mac := in.BridgePort.Spec.MacAddress vfport := vfInfo{ vfName: vfName, mac: net.HardwareAddr(mac).String(), } network.vfPort = append(network.vfPort, vfport) - vsp.networkStore[NfName] = network + vsp.networkStore[nfKey] = network } else { - vsp.networkStore[NfName] = mrvlNfPortMap{ + vsp.networkStore[nfKey] = mrvlNfPortMap{ vfPort: []vfInfo{ { vfName: vfName, @@ -409,20 +440,20 @@ func (vsp *mrvlVspServer) CreateBridgePort(ctx context.Context, in *opi.CreateBr if vsp.isNF { klog.Info("Marvell Store looks like", vsp.networkStore) mac := net.HardwareAddr(in.BridgePort.Spec.MacAddress).String() - // Add Flow rule from vfName to inPort (where in_port=vfname action=out_port=vsp.networkStore[NfName].inpPort) - if err := vsp.mrvlDP.AddFlowRuleToDataPlane(vsp.bridgeName, vfName, vsp.networkStore[NfName].inpPort, ""); err != nil { + // Add Flow rule from vfName to inPort (where in_port=vfname action=out_port=vsp.networkStore[nfKey].inpPort) + if err := vsp.mrvlDP.AddFlowRuleToDataPlane(vsp.bridgeName, vfName, vsp.networkStore[nfKey].inpPort, ""); err != nil { klog.Errorf("Error occurred in adding Flow Rule: %v", err) return nil, err } - // Add flow rule from inPort to vfName (where in_port=vsp.networkStore[NfName].inpPort action=out_port=vfName) + // Add flow rule from inPort to vfName (where in_port=vsp.networkStore[nfKey].inpPort action=out_port=vfName) // TODO: check if this can be done with Mac Learning? - if err := vsp.mrvlDP.AddFlowRuleToDataPlane(vsp.bridgeName, vsp.networkStore[NfName].inpPort, vfName, mac); err != nil { + if err := vsp.mrvlDP.AddFlowRuleToDataPlane(vsp.bridgeName, vsp.networkStore[nfKey].inpPort, vfName, mac); err != nil { klog.Errorf("Error occurred in adding Flow Rule: %v", err) return nil, err } // Add Hairpinning Flow Rule based on MAC Address - if err := vsp.mrvlDP.AddFlowRuleToDataPlane(vsp.bridgeName, vsp.networkStore[NfName].outPort, vsp.networkStore[NfName].outPort, mac); err != nil { + if err := vsp.mrvlDP.AddFlowRuleToDataPlane(vsp.bridgeName, vsp.networkStore[nfKey].outPort, vsp.networkStore[nfKey].outPort, mac); err != nil { klog.Errorf("Error occurred in adding Flow Rule: %v", err) return nil, err } @@ -451,6 +482,12 @@ func (vsp *mrvlVspServer) CreateBridgePort(ctx context.Context, in *opi.CreateBr func (vsp *mrvlVspServer) DeleteBridgePort(ctx context.Context, in *opi.DeleteBridgePortRequest) (*emptypb.Empty, error) { klog.Infof("Received DeleteBridgePort() request: Name: %v, AllowMissing: %v", in.Name, in.AllowMissing) portName := in.Name + + if vsp.isAccelerated { + klog.Infof("Accelerated mode: DeleteBridgePort for %s — returning success", portName) + return &emptypb.Empty{}, nil + } + vfName, _, err := vsp.getVFDetails(portName) klog.Infof("VF Name: %s", vfName) if err != nil { @@ -514,11 +551,29 @@ func (vsp *mrvlVspServer) DeleteBridgePort(ctx context.Context, in *opi.DeleteBr // CreateNetworkFunction function to create a network function with the given context and NFRequest // It will return the Empty and error func (vsp *mrvlVspServer) CreateNetworkFunction(ctx context.Context, in *nfapi.NFRequest) (*nfapi.Empty, error) { - klog.Infof("Received CreateNetworkFunction() request: Input: %v, Output: %v", in.Input, in.Output) + nfKey := in.BridgeId + if nfKey == "" { + nfKey = NfName + } + klog.Infof("Received CreateNetworkFunction() request: Input: %v, Output: %v, BridgeId: %v", in.Input, in.Output, nfKey) vsp.isNF = true + // Create Network Function in Accelerated mode + if vsp.isAccelerated { + network, exists := vsp.networkStore[nfKey] + if !exists { + network = mrvlNfPortMap{} + } + if in.Input != "" { + network.vfPort = append(network.vfPort, vfInfo{vfName: in.Input, mac: in.Input}) // TODO: Add vfName derived from MAC + } + vsp.networkStore[nfKey] = network + klog.Infof("Accelerated mode: NF registered in store for bridgeID %s, vfPort: %v", nfKey, network.vfPort) + return &nfapi.Empty{}, nil + } + inpDpInterfaceName := vsp.deviceStore[in.Input].dpInterfaceName outDpInterfaceName := vsp.deviceStore[in.Output].dpInterfaceName - out, err := vsp.AddNetworkFunction(inpDpInterfaceName, outDpInterfaceName, NfName) + out, err := vsp.AddNetworkFunction(inpDpInterfaceName, outDpInterfaceName, nfKey) return out, err } @@ -588,11 +643,43 @@ func (vsp *mrvlVspServer) AddNetworkFunction(inpDpInterfaceName string, outDpInt return out, nil } func (vsp *mrvlVspServer) DeleteNetworkFunction(ctx context.Context, in *nfapi.NFRequest) (*nfapi.Empty, error) { - klog.Infof("Received DeleteNetworkFunction() request: Input: %v, Output: %v", in.Input, in.Output) + nfKey := in.BridgeId + if nfKey == "" { + nfKey = NfName + } + klog.Infof("Received DeleteNetworkFunction() request: Input: %v, Output: %v, BridgeId: %v", in.Input, in.Output, nfKey) + + if vsp.isAccelerated { + network, exists := vsp.networkStore[nfKey] + if !exists { + klog.Infof("Accelerated mode: no store entry for bridgeID %s, nothing to delete", nfKey) + return &nfapi.Empty{}, nil + } + + mac := in.Input + remaining := make([]vfInfo, 0, len(network.vfPort)) + for _, vf := range network.vfPort { + if vf.mac != mac { + remaining = append(remaining, vf) + } + } + klog.Infof("Accelerated mode: removed VF with mac %s from bridgeID %s (%d -> %d)", mac, nfKey, len(network.vfPort), len(remaining)) + + if len(remaining) == 0 { + delete(vsp.networkStore, nfKey) + vsp.isNF = false + klog.Infof("Accelerated mode: store empty, deleted entry for bridgeID %s", nfKey) + } else { + network.vfPort = remaining + vsp.networkStore[nfKey] = network + } + return &nfapi.Empty{}, nil + } + vsp.isNF = false inpDpInterfaceName := vsp.deviceStore[in.Input].dpInterfaceName outDpInterfaceName := vsp.deviceStore[in.Output].dpInterfaceName - out, err := vsp.DeleteNetworkFunctionPort(inpDpInterfaceName, outDpInterfaceName, NfName) + out, err := vsp.DeleteNetworkFunctionPort(inpDpInterfaceName, outDpInterfaceName, nfKey) return out, err } @@ -643,22 +730,68 @@ func (vsp *mrvlVspServer) DeleteNetworkFunctionPort(inpDpInterfaceName string, o return out, nil } +func (vsp *mrvlVspServer) getAcceleratedDevices() (map[string]*pb.Device, error) { + devices := make(map[string]*pb.Device) + + vfNames, err := mrvlutils.GetAllVfsNameByDeviceID(DPUdeviceID) + if err != nil { + return nil, fmt.Errorf("failed to enumerate SDP VFs: %w", err) + } + + // VF index 0 (enP2p1s0v0) is the management interface used by + // configureIP() for the gRPC control channel. The data-path VFs + // start at index 1 and map 1:1 to the host-side VFs: + + for _, name := range vfNames[1:] { + health := vsp.GetDeviceHealth(name) + devices[name] = &pb.Device{ + ID: name, + Health: health, + } + } + + rpmNames, err := mrvlutils.GetAllVfsNameByDeviceID(DpuRpmDeviceID) + if err != nil { + klog.Errorf("Failed to enumerate RPM devices for GetDevices in accelerated mode: %v", err) + } + for _, ifName := range rpmNames { + health := vsp.GetDeviceHealth(ifName) + devices["accelerated:"+ifName] = &pb.Device{ + ID: "accelerated:" + ifName, + Health: health, + } + } + + return devices, nil +} + // GetDevices function to get all the devices with the given context and Empty // It will return the DeviceListResponse and error func (vsp *mrvlVspServer) GetDevices(ctx context.Context, in *emptypb.Empty) (*pb.DeviceListResponse, error) { klog.Info("Received GetDevices() request") devices := make(map[string]*pb.Device) - if vsp.deviceStore == nil { - return nil, errors.New("device Store is empty") - } if vsp.isDPUMode { - for _, mrvlDeviceInfo := range vsp.deviceStore { - devices[mrvlDeviceInfo.secInterfaceName] = &pb.Device{ - ID: mrvlDeviceInfo.secInterfaceName, - Health: mrvlDeviceInfo.health, + if vsp.isAccelerated { + acceleratedDevices, err := vsp.getAcceleratedDevices() + if err != nil { + return nil, err + } + devices = acceleratedDevices + } else { + if vsp.deviceStore == nil { + return nil, errors.New("device Store is empty") + } + for _, mrvlDeviceInfo := range vsp.deviceStore { + devices[mrvlDeviceInfo.secInterfaceName] = &pb.Device{ + ID: mrvlDeviceInfo.secInterfaceName, + Health: mrvlDeviceInfo.health, + } } } } else { + if vsp.deviceStore == nil { + return nil, errors.New("device Store is empty") + } for _, mrvlDeviceInfo := range vsp.deviceStore { devices[mrvlDeviceInfo.pciAddress] = &pb.Device{ ID: mrvlDeviceInfo.pciAddress, @@ -763,6 +896,7 @@ func (vsp *mrvlVspServer) Listen() (net.Listener, error) { } vsp.grpcServer = grpc.NewServer() nfapi.RegisterNetworkFunctionServiceServer(vsp.grpcServer, vsp) + nfapi.RegisterDpuNetworkConfigServiceServer(vsp.grpcServer, vsp) pb.RegisterLifeCycleServiceServer(vsp.grpcServer, vsp) pb.RegisterDeviceServiceServer(vsp.grpcServer, vsp) opi.RegisterBridgePortServiceServer(vsp.grpcServer, vsp) diff --git a/internal/daemon/vendor-specific-plugins/marvell/mrvl-utils/mrvlutils.go b/internal/daemon/vendor-specific-plugins/marvell/mrvl-utils/mrvlutils.go index a31e61455..0012498ac 100644 --- a/internal/daemon/vendor-specific-plugins/marvell/mrvl-utils/mrvlutils.go +++ b/internal/daemon/vendor-specific-plugins/marvell/mrvl-utils/mrvlutils.go @@ -58,7 +58,8 @@ func GetAllVfsNameByDeviceID(deviceID string) ([]string, error) { for _, vfpci := range dpuVfsPCI { vfName, err := GetNameByPCI(vfpci) if err != nil { - return nil, err + klog.V(4).Infof("Skipping VF %s (likely in pod namespace): %v", vfpci, err) + continue } dpuVfsName = append(dpuVfsName, vfName) } diff --git a/internal/daemon/vendor-specific-plugins/mock-vsp/mockvsp.go b/internal/daemon/vendor-specific-plugins/mock-vsp/mockvsp.go index fa099df79..4c47ba4a5 100644 --- a/internal/daemon/vendor-specific-plugins/mock-vsp/mockvsp.go +++ b/internal/daemon/vendor-specific-plugins/mock-vsp/mockvsp.go @@ -19,14 +19,16 @@ import ( type vspServer struct { pb.UnimplementedLifeCycleServiceServer nfapi.UnimplementedNetworkFunctionServiceServer + nfapi.UnimplementedDpuNetworkConfigServiceServer pb.UnimplementedDeviceServiceServer opi.UnimplementedBridgePortServiceServer - log logr.Logger - wg sync.WaitGroup - startedWg sync.WaitGroup - done chan error - grpcServer *grpc.Server - pathManager utils.PathManager + log logr.Logger + wg sync.WaitGroup + startedWg sync.WaitGroup + done chan error + grpcServer *grpc.Server + pathManager utils.PathManager + isAccelerated bool } func (vsp *vspServer) Init(ctx context.Context, in *pb.InitRequest) (*pb.IpPort, error) { @@ -70,6 +72,12 @@ func (vsp *vspServer) DeleteNetworkFunction(ctx context.Context, in *nfapi.NFReq return nil, nil } +func (vsp *vspServer) SetDpuNetworkConfig(ctx context.Context, in *nfapi.DpuNetworkConfigRequest) (*nfapi.Empty, error) { + vsp.log.Info("Received SetDpuNetworkConfig() request", "IsAccelerated", in.IsAccelerated) + vsp.isAccelerated = in.IsAccelerated + return &nfapi.Empty{}, nil +} + func (vsp *vspServer) Listen() (net.Listener, error) { err := vsp.pathManager.EnsureSocketDirExists(vsp.pathManager.VendorPluginSocket()) if err != nil { @@ -83,6 +91,7 @@ func (vsp *vspServer) Listen() (net.Listener, error) { vsp.grpcServer = grpc.NewServer() nfapi.RegisterNetworkFunctionServiceServer(vsp.grpcServer, vsp) + nfapi.RegisterDpuNetworkConfigServiceServer(vsp.grpcServer, vsp) pb.RegisterLifeCycleServiceServer(vsp.grpcServer, vsp) pb.RegisterDeviceServiceServer(vsp.grpcServer, vsp) opi.RegisterBridgePortServiceServer(vsp.grpcServer, vsp) diff --git a/internal/utils/path_manager.go b/internal/utils/path_manager.go index a445bcf87..ce050c6a4 100644 --- a/internal/utils/path_manager.go +++ b/internal/utils/path_manager.go @@ -10,7 +10,8 @@ import ( ) type PathManager struct { - rootDir string + rootDir string + pluginEndpointPath string // if set, overrides the default dpuNet.sock path } func NewPathManager(rootDir string) *PathManager { @@ -27,13 +28,31 @@ func (p *PathManager) KubeletEndPoint() string { } func (p *PathManager) PluginEndpoint() string { + if p.pluginEndpointPath != "" { + return p.pluginEndpointPath + } return p.wrap("/var/lib/kubelet/device-plugins/dpuNet.sock") } +// PluginEndpointFor returns a unique socket path for the given suffix, +// e.g. PluginEndpointFor("net1") -> ".../dpuNet-net1.sock". +func (p *PathManager) PluginEndpointFor(suffix string) string { + return p.wrap(fmt.Sprintf("/var/lib/kubelet/device-plugins/dpuNet-%s.sock", suffix)) +} + func (p *PathManager) PluginEndpointFilename() string { return filepath.Base(p.PluginEndpoint()) } +// PathManagerFor returns a copy of this PathManager whose PluginEndpoint() +// returns PluginEndpointFor(suffix). Everything else stays the same. +func (p *PathManager) PathManagerFor(suffix string) PathManager { + return PathManager{ + rootDir: p.rootDir, + pluginEndpointPath: p.PluginEndpointFor(suffix), + } +} + func (p *PathManager) CniPath() string { return "/var/lib/cni/bin/dpu-cni" } diff --git a/vendor/github.com/k8snetworkplumbingwg/network-resources-injector/pkg/webhook/webhook.go b/vendor/github.com/k8snetworkplumbingwg/network-resources-injector/pkg/webhook/webhook.go index 2d8f05f5f..0639b7710 100644 --- a/vendor/github.com/k8snetworkplumbingwg/network-resources-injector/pkg/webhook/webhook.go +++ b/vendor/github.com/k8snetworkplumbingwg/network-resources-injector/pkg/webhook/webhook.go @@ -787,6 +787,44 @@ func MutateHandler(w http.ResponseWriter, req *http.Request) { defaultNetSelection, defExist := getNetworkSelections(defaultNetworkAnnotationKey, pod, userDefinedPatch) additionalNetSelections, addExists := getNetworkSelections(networksAnnotationKey, pod, userDefinedPatch) + dpuNetInjected := false + if !addExists { + if dpuNet, ok := pod.ObjectMeta.Annotations["dpu.config.openshift.io/dpu-network"]; ok && dpuNet != "" { + nadName := dpuNet + "-nad" + count := int64(0) + resName := corev1.ResourceName("openshift.io/dpunetwork-" + dpuNet) + for _, c := range pod.Spec.Containers { + if q, exists := c.Resources.Requests[resName]; exists { + count += q.Value() + } + } + if count > 0 { + parts := make([]string, count) + for i := range parts { + parts[i] = nadName + } + + accelNadName := "dpu-accel-nad" + accelResName := corev1.ResourceName("openshift.io/dpu-accelerated") + accelCount := int64(0) + for _, c := range pod.Spec.Containers { + if q, exists := c.Resources.Requests[accelResName]; exists { + accelCount += q.Value() + } + } + for i := int64(0); i < accelCount; i++ { + parts = append(parts, accelNadName) + } + + additionalNetSelections = strings.Join(parts, ",") + addExists = true + dpuNetInjected = true + glog.Infof("DPU network injection: injecting %d NAD references (%d VF + %d accelerated) for network %s on pod %s/%s", + count+accelCount, count, accelCount, dpuNet, pod.ObjectMeta.Namespace, pod.ObjectMeta.Name) + } + } + } + if defExist || addExists { /* map of resources request needed by a pod and a number of them */ resourceRequests := make(map[string]int64) @@ -918,6 +956,23 @@ func MutateHandler(w http.ResponseWriter, req *http.Request) { patch = appendUserDefinedPatch(patch, pod, userDefinedPatch) } patch = createNodeSelectorPatch(patch, pod.Spec.NodeSelector, desiredNsMap) + + if dpuNetInjected { + if pod.ObjectMeta.Annotations == nil { + patch = append(patch, types.JsonPatchOperation{ + Operation: "add", + Path: "/metadata/annotations", + Value: map[string]string{networksAnnotationKey: additionalNetSelections}, + }) + } else { + patch = append(patch, types.JsonPatchOperation{ + Operation: "add", + Path: "/metadata/annotations/k8s.v1.cni.cncf.io~1networks", + Value: additionalNetSelections, + }) + } + } + glog.Infof("patch after all mutations: %v for pod %s/%s", patch, pod.ObjectMeta.Namespace, pod.ObjectMeta.Name) patchBytes, _ := json.Marshal(patch) diff --git a/vendor/github.com/openshift/dpu-operator/api/v1/dpunetwork_types.go b/vendor/github.com/openshift/dpu-operator/api/v1/dpunetwork_types.go new file mode 100644 index 000000000..094bb072e --- /dev/null +++ b/vendor/github.com/openshift/dpu-operator/api/v1/dpunetwork_types.go @@ -0,0 +1,85 @@ +/* +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" +) + +// DpuNetworkSpec defines the desired state of DpuNetwork. +type DpuNetworkSpec struct { + // NodeSelector specifies which nodes this DpuNetwork should apply to. + // If empty, the DpuNetwork will apply to all nodes. + // +optional + NodeSelector *metav1.LabelSelector `json:"nodeSelector,omitempty"` + + // DpuSelector specifies which DPUs (and their VFs) this DpuNetwork targets. + // + // Note: Today this is treated as an opaque selector definition; the controller + // parses vfId ranges from matchExpressions (if present) but does not yet + // validate against a per-VF inventory. + // +optional + DpuSelector *metav1.LabelSelector `json:"dpuSelector,omitempty"` + + // IsAccelerated indicates whether the network should be treated as accelerated + // by downstream components. + // +optional + IsAccelerated bool `json:"isAccelerated,omitempty"` +} + +// DpuNetworkStatus defines the observed state of DpuNetwork. +type DpuNetworkStatus struct { + // Conditions is the status of the DpuNetwork. + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` + + // ResourceName is the Kubernetes extended resource name generated for this network. + // +optional + ResourceName string `json:"resourceName,omitempty"` + + // SelectedVFs is the list of VF IDs parsed from vfId ranges. + // +optional + SelectedVFs []int32 `json:"selectedVFs,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Cluster,shortName=dpunet +//+kubebuilder:printcolumn:name="Resource",type="string",JSONPath=".status.resourceName" +//+kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" + +// DpuNetwork is the Schema for the dpunetworks API. +type DpuNetwork struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec DpuNetworkSpec `json:"spec,omitempty"` + Status DpuNetworkStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// DpuNetworkList contains a list of DpuNetwork. +type DpuNetworkList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []DpuNetwork `json:"items"` +} + +func init() { + SchemeBuilder.Register(&DpuNetwork{}, &DpuNetworkList{}) +} diff --git a/vendor/github.com/openshift/dpu-operator/api/v1/zz_generated.deepcopy.go b/vendor/github.com/openshift/dpu-operator/api/v1/zz_generated.deepcopy.go index 17645862a..0623e9e78 100644 --- a/vendor/github.com/openshift/dpu-operator/api/v1/zz_generated.deepcopy.go +++ b/vendor/github.com/openshift/dpu-operator/api/v1/zz_generated.deepcopy.go @@ -215,6 +215,117 @@ func (in *DataProcessingUnitStatus) DeepCopy() *DataProcessingUnitStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DpuNetwork) DeepCopyInto(out *DpuNetwork) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DpuNetwork. +func (in *DpuNetwork) DeepCopy() *DpuNetwork { + if in == nil { + return nil + } + out := new(DpuNetwork) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DpuNetwork) 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 *DpuNetworkList) DeepCopyInto(out *DpuNetworkList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]DpuNetwork, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DpuNetworkList. +func (in *DpuNetworkList) DeepCopy() *DpuNetworkList { + if in == nil { + return nil + } + out := new(DpuNetworkList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DpuNetworkList) 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 *DpuNetworkSpec) DeepCopyInto(out *DpuNetworkSpec) { + *out = *in + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.DpuSelector != nil { + in, out := &in.DpuSelector, &out.DpuSelector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DpuNetworkSpec. +func (in *DpuNetworkSpec) DeepCopy() *DpuNetworkSpec { + if in == nil { + return nil + } + out := new(DpuNetworkSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DpuNetworkStatus) DeepCopyInto(out *DpuNetworkStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SelectedVFs != nil { + in, out := &in.SelectedVFs, &out.SelectedVFs + *out = make([]int32, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DpuNetworkStatus. +func (in *DpuNetworkStatus) DeepCopy() *DpuNetworkStatus { + if in == nil { + return nil + } + out := new(DpuNetworkStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DpuOperatorConfig) DeepCopyInto(out *DpuOperatorConfig) { *out = *in diff --git a/vendor/github.com/openshift/dpu-operator/dpu-api/gen/api.pb.go b/vendor/github.com/openshift/dpu-operator/dpu-api/gen/api.pb.go index e9f87bfb8..425ca4e7e 100644 --- a/vendor/github.com/openshift/dpu-operator/dpu-api/gen/api.pb.go +++ b/vendor/github.com/openshift/dpu-operator/dpu-api/gen/api.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.6 +// protoc-gen-go v1.36.10 // protoc v3.19.6 // source: api.proto @@ -129,6 +129,7 @@ type NFRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Input string `protobuf:"bytes,1,opt,name=input,proto3" json:"input,omitempty"` Output string `protobuf:"bytes,2,opt,name=output,proto3" json:"output,omitempty"` + BridgeId string `protobuf:"bytes,3,opt,name=bridge_id,json=bridgeId,proto3" json:"bridge_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -177,6 +178,57 @@ func (x *NFRequest) GetOutput() string { return "" } +func (x *NFRequest) GetBridgeId() string { + if x != nil { + return x.BridgeId + } + return "" +} + +type DpuNetworkConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + IsAccelerated bool `protobuf:"varint,1,opt,name=is_accelerated,json=isAccelerated,proto3" json:"is_accelerated,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DpuNetworkConfigRequest) Reset() { + *x = DpuNetworkConfigRequest{} + mi := &file_api_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DpuNetworkConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DpuNetworkConfigRequest) ProtoMessage() {} + +func (x *DpuNetworkConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DpuNetworkConfigRequest.ProtoReflect.Descriptor instead. +func (*DpuNetworkConfigRequest) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{3} +} + +func (x *DpuNetworkConfigRequest) GetIsAccelerated() bool { + if x != nil { + return x.IsAccelerated + } + return false +} + type Empty struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -185,7 +237,7 @@ type Empty struct { func (x *Empty) Reset() { *x = Empty{} - mi := &file_api_proto_msgTypes[3] + mi := &file_api_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -197,7 +249,7 @@ func (x *Empty) String() string { func (*Empty) ProtoMessage() {} func (x *Empty) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[3] + mi := &file_api_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -210,7 +262,7 @@ func (x *Empty) ProtoReflect() protoreflect.Message { // Deprecated: Use Empty.ProtoReflect.Descriptor instead. func (*Empty) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{3} + return file_api_proto_rawDescGZIP(), []int{4} } type VfCount struct { @@ -222,7 +274,7 @@ type VfCount struct { func (x *VfCount) Reset() { *x = VfCount{} - mi := &file_api_proto_msgTypes[4] + mi := &file_api_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -234,7 +286,7 @@ func (x *VfCount) String() string { func (*VfCount) ProtoMessage() {} func (x *VfCount) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[4] + mi := &file_api_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -247,7 +299,7 @@ func (x *VfCount) ProtoReflect() protoreflect.Message { // Deprecated: Use VfCount.ProtoReflect.Descriptor instead. func (*VfCount) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{4} + return file_api_proto_rawDescGZIP(), []int{5} } func (x *VfCount) GetVfCnt() int32 { @@ -266,7 +318,7 @@ type TopologyInfo struct { func (x *TopologyInfo) Reset() { *x = TopologyInfo{} - mi := &file_api_proto_msgTypes[5] + mi := &file_api_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -278,7 +330,7 @@ func (x *TopologyInfo) String() string { func (*TopologyInfo) ProtoMessage() {} func (x *TopologyInfo) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[5] + mi := &file_api_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -291,7 +343,7 @@ func (x *TopologyInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use TopologyInfo.ProtoReflect.Descriptor instead. func (*TopologyInfo) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{5} + return file_api_proto_rawDescGZIP(), []int{6} } func (x *TopologyInfo) GetNode() string { @@ -312,7 +364,7 @@ type Device struct { func (x *Device) Reset() { *x = Device{} - mi := &file_api_proto_msgTypes[6] + mi := &file_api_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -324,7 +376,7 @@ func (x *Device) String() string { func (*Device) ProtoMessage() {} func (x *Device) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[6] + mi := &file_api_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -337,7 +389,7 @@ func (x *Device) ProtoReflect() protoreflect.Message { // Deprecated: Use Device.ProtoReflect.Descriptor instead. func (*Device) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{6} + return file_api_proto_rawDescGZIP(), []int{7} } func (x *Device) GetID() string { @@ -370,7 +422,7 @@ type DeviceListResponse struct { func (x *DeviceListResponse) Reset() { *x = DeviceListResponse{} - mi := &file_api_proto_msgTypes[7] + mi := &file_api_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -382,7 +434,7 @@ func (x *DeviceListResponse) String() string { func (*DeviceListResponse) ProtoMessage() {} func (x *DeviceListResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[7] + mi := &file_api_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -395,7 +447,7 @@ func (x *DeviceListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeviceListResponse.ProtoReflect.Descriptor instead. func (*DeviceListResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{7} + return file_api_proto_rawDescGZIP(), []int{8} } func (x *DeviceListResponse) GetDevices() map[string]*Device { @@ -415,7 +467,7 @@ type PingRequest struct { func (x *PingRequest) Reset() { *x = PingRequest{} - mi := &file_api_proto_msgTypes[8] + mi := &file_api_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -427,7 +479,7 @@ func (x *PingRequest) String() string { func (*PingRequest) ProtoMessage() {} func (x *PingRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[8] + mi := &file_api_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -440,7 +492,7 @@ func (x *PingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PingRequest.ProtoReflect.Descriptor instead. func (*PingRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{8} + return file_api_proto_rawDescGZIP(), []int{9} } func (x *PingRequest) GetTimestamp() int64 { @@ -468,7 +520,7 @@ type PingResponse struct { func (x *PingResponse) Reset() { *x = PingResponse{} - mi := &file_api_proto_msgTypes[9] + mi := &file_api_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -480,7 +532,7 @@ func (x *PingResponse) String() string { func (*PingResponse) ProtoMessage() {} func (x *PingResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[9] + mi := &file_api_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -493,7 +545,7 @@ func (x *PingResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PingResponse.ProtoReflect.Descriptor instead. func (*PingResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{9} + return file_api_proto_rawDescGZIP(), []int{10} } func (x *PingResponse) GetTimestamp() int64 { @@ -527,10 +579,13 @@ const file_api_proto_rawDesc = "" + "\x0edpu_identifier\x18\x02 \x01(\tR\rdpuIdentifier\",\n" + "\x06IpPort\x12\x0e\n" + "\x02ip\x18\x01 \x01(\tR\x02ip\x12\x12\n" + - "\x04port\x18\x02 \x01(\x05R\x04port\"9\n" + + "\x04port\x18\x02 \x01(\x05R\x04port\"V\n" + "\tNFRequest\x12\x14\n" + "\x05input\x18\x01 \x01(\tR\x05input\x12\x16\n" + - "\x06output\x18\x02 \x01(\tR\x06output\"\a\n" + + "\x06output\x18\x02 \x01(\tR\x06output\x12\x1b\n" + + "\tbridge_id\x18\x03 \x01(\tR\bbridgeId\"@\n" + + "\x17DpuNetworkConfigRequest\x12%\n" + + "\x0eis_accelerated\x18\x01 \x01(\bR\risAccelerated\"\a\n" + "\x05Empty\" \n" + "\aVfCount\x12\x15\n" + "\x06vf_cnt\x18\x01 \x01(\x05R\x05vfCnt\"\"\n" + @@ -553,7 +608,9 @@ const file_api_proto_rawDesc = "" + "\fresponder_id\x18\x02 \x01(\tR\vresponderId\x12\x18\n" + "\ahealthy\x18\x03 \x01(\bR\ahealthy2?\n" + "\x10LifeCycleService\x12+\n" + - "\x04Init\x12\x13.Vendor.InitRequest\x1a\x0e.Vendor.IpPort2\x8e\x01\n" + + "\x04Init\x12\x13.Vendor.InitRequest\x1a\x0e.Vendor.IpPort2`\n" + + "\x17DpuNetworkConfigService\x12E\n" + + "\x13SetDpuNetworkConfig\x12\x1f.Vendor.DpuNetworkConfigRequest\x1a\r.Vendor.Empty2\x8e\x01\n" + "\x16NetworkFunctionService\x129\n" + "\x15CreateNetworkFunction\x12\x11.Vendor.NFRequest\x1a\r.Vendor.Empty\x129\n" + "\x15DeleteNetworkFunction\x12\x11.Vendor.NFRequest\x1a\r.Vendor.Empty2w\n" + @@ -576,38 +633,41 @@ func file_api_proto_rawDescGZIP() []byte { return file_api_proto_rawDescData } -var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_api_proto_goTypes = []any{ - (*InitRequest)(nil), // 0: Vendor.InitRequest - (*IpPort)(nil), // 1: Vendor.IpPort - (*NFRequest)(nil), // 2: Vendor.NFRequest - (*Empty)(nil), // 3: Vendor.Empty - (*VfCount)(nil), // 4: Vendor.VfCount - (*TopologyInfo)(nil), // 5: Vendor.TopologyInfo - (*Device)(nil), // 6: Vendor.Device - (*DeviceListResponse)(nil), // 7: Vendor.DeviceListResponse - (*PingRequest)(nil), // 8: Vendor.PingRequest - (*PingResponse)(nil), // 9: Vendor.PingResponse - nil, // 10: Vendor.DeviceListResponse.DevicesEntry + (*InitRequest)(nil), // 0: Vendor.InitRequest + (*IpPort)(nil), // 1: Vendor.IpPort + (*NFRequest)(nil), // 2: Vendor.NFRequest + (*DpuNetworkConfigRequest)(nil), // 3: Vendor.DpuNetworkConfigRequest + (*Empty)(nil), // 4: Vendor.Empty + (*VfCount)(nil), // 5: Vendor.VfCount + (*TopologyInfo)(nil), // 6: Vendor.TopologyInfo + (*Device)(nil), // 7: Vendor.Device + (*DeviceListResponse)(nil), // 8: Vendor.DeviceListResponse + (*PingRequest)(nil), // 9: Vendor.PingRequest + (*PingResponse)(nil), // 10: Vendor.PingResponse + nil, // 11: Vendor.DeviceListResponse.DevicesEntry } var file_api_proto_depIdxs = []int32{ - 5, // 0: Vendor.Device.topology:type_name -> Vendor.TopologyInfo - 10, // 1: Vendor.DeviceListResponse.devices:type_name -> Vendor.DeviceListResponse.DevicesEntry - 6, // 2: Vendor.DeviceListResponse.DevicesEntry.value:type_name -> Vendor.Device + 6, // 0: Vendor.Device.topology:type_name -> Vendor.TopologyInfo + 11, // 1: Vendor.DeviceListResponse.devices:type_name -> Vendor.DeviceListResponse.DevicesEntry + 7, // 2: Vendor.DeviceListResponse.DevicesEntry.value:type_name -> Vendor.Device 0, // 3: Vendor.LifeCycleService.Init:input_type -> Vendor.InitRequest - 2, // 4: Vendor.NetworkFunctionService.CreateNetworkFunction:input_type -> Vendor.NFRequest - 2, // 5: Vendor.NetworkFunctionService.DeleteNetworkFunction:input_type -> Vendor.NFRequest - 3, // 6: Vendor.DeviceService.GetDevices:input_type -> Vendor.Empty - 4, // 7: Vendor.DeviceService.SetNumVfs:input_type -> Vendor.VfCount - 8, // 8: Vendor.HeartbeatService.Ping:input_type -> Vendor.PingRequest - 1, // 9: Vendor.LifeCycleService.Init:output_type -> Vendor.IpPort - 3, // 10: Vendor.NetworkFunctionService.CreateNetworkFunction:output_type -> Vendor.Empty - 3, // 11: Vendor.NetworkFunctionService.DeleteNetworkFunction:output_type -> Vendor.Empty - 7, // 12: Vendor.DeviceService.GetDevices:output_type -> Vendor.DeviceListResponse - 4, // 13: Vendor.DeviceService.SetNumVfs:output_type -> Vendor.VfCount - 9, // 14: Vendor.HeartbeatService.Ping:output_type -> Vendor.PingResponse - 9, // [9:15] is the sub-list for method output_type - 3, // [3:9] is the sub-list for method input_type + 3, // 4: Vendor.DpuNetworkConfigService.SetDpuNetworkConfig:input_type -> Vendor.DpuNetworkConfigRequest + 2, // 5: Vendor.NetworkFunctionService.CreateNetworkFunction:input_type -> Vendor.NFRequest + 2, // 6: Vendor.NetworkFunctionService.DeleteNetworkFunction:input_type -> Vendor.NFRequest + 4, // 7: Vendor.DeviceService.GetDevices:input_type -> Vendor.Empty + 5, // 8: Vendor.DeviceService.SetNumVfs:input_type -> Vendor.VfCount + 9, // 9: Vendor.HeartbeatService.Ping:input_type -> Vendor.PingRequest + 1, // 10: Vendor.LifeCycleService.Init:output_type -> Vendor.IpPort + 4, // 11: Vendor.DpuNetworkConfigService.SetDpuNetworkConfig:output_type -> Vendor.Empty + 4, // 12: Vendor.NetworkFunctionService.CreateNetworkFunction:output_type -> Vendor.Empty + 4, // 13: Vendor.NetworkFunctionService.DeleteNetworkFunction:output_type -> Vendor.Empty + 8, // 14: Vendor.DeviceService.GetDevices:output_type -> Vendor.DeviceListResponse + 5, // 15: Vendor.DeviceService.SetNumVfs:output_type -> Vendor.VfCount + 10, // 16: Vendor.HeartbeatService.Ping:output_type -> Vendor.PingResponse + 10, // [10:17] is the sub-list for method output_type + 3, // [3:10] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name @@ -624,9 +684,9 @@ func file_api_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_rawDesc), len(file_api_proto_rawDesc)), NumEnums: 0, - NumMessages: 11, + NumMessages: 12, NumExtensions: 0, - NumServices: 4, + NumServices: 5, }, GoTypes: file_api_proto_goTypes, DependencyIndexes: file_api_proto_depIdxs, diff --git a/vendor/github.com/openshift/dpu-operator/dpu-api/gen/api_grpc.pb.go b/vendor/github.com/openshift/dpu-operator/dpu-api/gen/api_grpc.pb.go index 96ce737b7..90e67fbc2 100644 --- a/vendor/github.com/openshift/dpu-operator/dpu-api/gen/api_grpc.pb.go +++ b/vendor/github.com/openshift/dpu-operator/dpu-api/gen/api_grpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.5.1 +// - protoc-gen-go-grpc v1.6.1 // - protoc v3.19.6 // source: api.proto @@ -63,7 +63,7 @@ type LifeCycleServiceServer interface { type UnimplementedLifeCycleServiceServer struct{} func (UnimplementedLifeCycleServiceServer) Init(context.Context, *InitRequest) (*IpPort, error) { - return nil, status.Errorf(codes.Unimplemented, "method Init not implemented") + return nil, status.Error(codes.Unimplemented, "method Init not implemented") } func (UnimplementedLifeCycleServiceServer) mustEmbedUnimplementedLifeCycleServiceServer() {} func (UnimplementedLifeCycleServiceServer) testEmbeddedByValue() {} @@ -76,7 +76,7 @@ type UnsafeLifeCycleServiceServer interface { } func RegisterLifeCycleServiceServer(s grpc.ServiceRegistrar, srv LifeCycleServiceServer) { - // If the following call pancis, it indicates UnimplementedLifeCycleServiceServer was + // If the following call panics, it indicates UnimplementedLifeCycleServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. @@ -120,6 +120,109 @@ var LifeCycleService_ServiceDesc = grpc.ServiceDesc{ Metadata: "api.proto", } +const ( + DpuNetworkConfigService_SetDpuNetworkConfig_FullMethodName = "/Vendor.DpuNetworkConfigService/SetDpuNetworkConfig" +) + +// DpuNetworkConfigServiceClient is the client API for DpuNetworkConfigService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type DpuNetworkConfigServiceClient interface { + SetDpuNetworkConfig(ctx context.Context, in *DpuNetworkConfigRequest, opts ...grpc.CallOption) (*Empty, error) +} + +type dpuNetworkConfigServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewDpuNetworkConfigServiceClient(cc grpc.ClientConnInterface) DpuNetworkConfigServiceClient { + return &dpuNetworkConfigServiceClient{cc} +} + +func (c *dpuNetworkConfigServiceClient) SetDpuNetworkConfig(ctx context.Context, in *DpuNetworkConfigRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Empty) + err := c.cc.Invoke(ctx, DpuNetworkConfigService_SetDpuNetworkConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// DpuNetworkConfigServiceServer is the server API for DpuNetworkConfigService service. +// All implementations must embed UnimplementedDpuNetworkConfigServiceServer +// for forward compatibility. +type DpuNetworkConfigServiceServer interface { + SetDpuNetworkConfig(context.Context, *DpuNetworkConfigRequest) (*Empty, error) + mustEmbedUnimplementedDpuNetworkConfigServiceServer() +} + +// UnimplementedDpuNetworkConfigServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedDpuNetworkConfigServiceServer struct{} + +func (UnimplementedDpuNetworkConfigServiceServer) SetDpuNetworkConfig(context.Context, *DpuNetworkConfigRequest) (*Empty, error) { + return nil, status.Error(codes.Unimplemented, "method SetDpuNetworkConfig not implemented") +} +func (UnimplementedDpuNetworkConfigServiceServer) mustEmbedUnimplementedDpuNetworkConfigServiceServer() { +} +func (UnimplementedDpuNetworkConfigServiceServer) testEmbeddedByValue() {} + +// UnsafeDpuNetworkConfigServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to DpuNetworkConfigServiceServer will +// result in compilation errors. +type UnsafeDpuNetworkConfigServiceServer interface { + mustEmbedUnimplementedDpuNetworkConfigServiceServer() +} + +func RegisterDpuNetworkConfigServiceServer(s grpc.ServiceRegistrar, srv DpuNetworkConfigServiceServer) { + // If the following call panics, it indicates UnimplementedDpuNetworkConfigServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&DpuNetworkConfigService_ServiceDesc, srv) +} + +func _DpuNetworkConfigService_SetDpuNetworkConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DpuNetworkConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DpuNetworkConfigServiceServer).SetDpuNetworkConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DpuNetworkConfigService_SetDpuNetworkConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DpuNetworkConfigServiceServer).SetDpuNetworkConfig(ctx, req.(*DpuNetworkConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// DpuNetworkConfigService_ServiceDesc is the grpc.ServiceDesc for DpuNetworkConfigService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var DpuNetworkConfigService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "Vendor.DpuNetworkConfigService", + HandlerType: (*DpuNetworkConfigServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SetDpuNetworkConfig", + Handler: _DpuNetworkConfigService_SetDpuNetworkConfig_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api.proto", +} + const ( NetworkFunctionService_CreateNetworkFunction_FullMethodName = "/Vendor.NetworkFunctionService/CreateNetworkFunction" NetworkFunctionService_DeleteNetworkFunction_FullMethodName = "/Vendor.NetworkFunctionService/DeleteNetworkFunction" @@ -178,10 +281,10 @@ type NetworkFunctionServiceServer interface { type UnimplementedNetworkFunctionServiceServer struct{} func (UnimplementedNetworkFunctionServiceServer) CreateNetworkFunction(context.Context, *NFRequest) (*Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method CreateNetworkFunction not implemented") + return nil, status.Error(codes.Unimplemented, "method CreateNetworkFunction not implemented") } func (UnimplementedNetworkFunctionServiceServer) DeleteNetworkFunction(context.Context, *NFRequest) (*Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method DeleteNetworkFunction not implemented") + return nil, status.Error(codes.Unimplemented, "method DeleteNetworkFunction not implemented") } func (UnimplementedNetworkFunctionServiceServer) mustEmbedUnimplementedNetworkFunctionServiceServer() { } @@ -195,7 +298,7 @@ type UnsafeNetworkFunctionServiceServer interface { } func RegisterNetworkFunctionServiceServer(s grpc.ServiceRegistrar, srv NetworkFunctionServiceServer) { - // If the following call pancis, it indicates UnimplementedNetworkFunctionServiceServer was + // If the following call panics, it indicates UnimplementedNetworkFunctionServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. @@ -319,10 +422,10 @@ type DeviceServiceServer interface { type UnimplementedDeviceServiceServer struct{} func (UnimplementedDeviceServiceServer) GetDevices(context.Context, *Empty) (*DeviceListResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetDevices not implemented") + return nil, status.Error(codes.Unimplemented, "method GetDevices not implemented") } func (UnimplementedDeviceServiceServer) SetNumVfs(context.Context, *VfCount) (*VfCount, error) { - return nil, status.Errorf(codes.Unimplemented, "method SetNumVfs not implemented") + return nil, status.Error(codes.Unimplemented, "method SetNumVfs not implemented") } func (UnimplementedDeviceServiceServer) mustEmbedUnimplementedDeviceServiceServer() {} func (UnimplementedDeviceServiceServer) testEmbeddedByValue() {} @@ -335,7 +438,7 @@ type UnsafeDeviceServiceServer interface { } func RegisterDeviceServiceServer(s grpc.ServiceRegistrar, srv DeviceServiceServer) { - // If the following call pancis, it indicates UnimplementedDeviceServiceServer was + // If the following call panics, it indicates UnimplementedDeviceServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. @@ -446,7 +549,7 @@ type HeartbeatServiceServer interface { type UnimplementedHeartbeatServiceServer struct{} func (UnimplementedHeartbeatServiceServer) Ping(context.Context, *PingRequest) (*PingResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented") + return nil, status.Error(codes.Unimplemented, "method Ping not implemented") } func (UnimplementedHeartbeatServiceServer) mustEmbedUnimplementedHeartbeatServiceServer() {} func (UnimplementedHeartbeatServiceServer) testEmbeddedByValue() {} @@ -459,7 +562,7 @@ type UnsafeHeartbeatServiceServer interface { } func RegisterHeartbeatServiceServer(s grpc.ServiceRegistrar, srv HeartbeatServiceServer) { - // If the following call pancis, it indicates UnimplementedHeartbeatServiceServer was + // If the following call panics, it indicates UnimplementedHeartbeatServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O.