diff --git a/PROJECT b/PROJECT index fea9a3abf..bf2c35e79 100644 --- a/PROJECT +++ b/PROJECT @@ -4,82 +4,129 @@ # More info: https://book.kubebuilder.io/reference/project-config.html domain: bpfman.io layout: - - go.kubebuilder.io/v3 +- go.kubebuilder.io/v3 plugins: manifests.sdk.operatorframework.io/v2: {} scorecard.sdk.operatorframework.io/v2: {} projectName: bpfman-operator repo: github.com/bpfman resources: - - api: - crdVersion: v1 - namespaced: true - controller: true - domain: bpfman.io - kind: BpfProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 - - api: - crdVersion: v1 - controller: true - domain: bpfman.io - kind: XdpProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 - - api: - crdVersion: v1 - controller: true - domain: bpfman.io - kind: TcProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 - - api: - crdVersion: v1 - controller: true - domain: bpfman.io - kind: TcxProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 - - api: - crdVersion: v1 - controller: true - domain: bpfman.io - kind: TracePointProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 - - api: - crdVersion: v1 - controller: true - domain: bpfman.io - kind: KprobeProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 - - api: - crdVersion: v1 - controller: true - domain: bpfman.io - kind: UprobeProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 - - api: - crdVersion: v1 - controller: true - domain: bpfman.io - kind: FentryProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 - - api: - crdVersion: v1 - controller: true - domain: bpfman.io - kind: FexitProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 - - api: - crdVersion: v1 - controller: true - domain: bpfman.io - kind: BpfApplication - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: BpfProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: XdpProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: TcProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: TcxProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: TracePointProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: KprobeProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: UprobeProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: FentryProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: FexitProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: BpfApplication + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: bpfman.io + kind: BpfNsProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: bpfman.io + kind: XdpNsProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: bpfman.io + kind: TcNsProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: bpfman.io + kind: TcxNsProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: bpfman.io + kind: UprobeNsProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: bpfman.io + kind: BpfNsApplication + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 version: "3" diff --git a/README.md b/README.md index 931e9dd19..81b39acdf 100644 --- a/README.md +++ b/README.md @@ -173,32 +173,48 @@ xdp-pass-all-nodes pass {} 0 {"primarynodein ## API Types Overview -Refer to [api-spec.md](https://bpfman.io/main/developer-guide/api-spec/) for a detailed description of all the bpfman Kubernetes API types. +Refer to [api-spec.md](https://bpfman.io/main/developer-guide/api-spec/) for a more detailed description of all the +bpfman Kubernetes API types. + +### Cluster Scoped Versus Namespaced Scoped CRDs + +For security reasons, cluster admins may want to limit certain applications to only loading eBPF programs +within a given namespace. +To provide these tighter controls on eBPF program loading, some of the bpfman Custom Resource Definitions (CRDs) +are Namespace scoped. +Not all eBPF programs make sense to be namespaced scoped. +The namespaced scoped CRDs use the "NsProgram" identifier and cluster scoped CRDs to use "Program" +identifier. ### Multiple Program CRDs -The multiple `*Program` CRDs are the bpfman Kubernetes API objects most relevant to users. -They express how and where eBPF programs are to be deployed within a Kubernetes cluster. Currently, -bpfman supports: +The multiple `*Program` CRDs are the bpfman Kubernetes API objects most relevant to users and can be used to +understand clusterwide state for an eBPF program. +It's designed to express how, and where eBPF programs are to be deployed within a Kubernetes cluster. +Currently bpfman supports: * `fentryProgram` * `fexitProgram` * `kprobeProgram` -* `tcProgram` +* `tcProgram` and `tcNsProgram` +* `tcxProgram` and `tcxNsProgram` * `tracepointProgram` -* `uprobeProgram` -* `xdpProgram` - -## BpfApplication CRD - -The `BpfApplication` CRD is designed for managing eBPF programs at an application level within a Kubernetes cluster. -This CRD allows Kubernetes users to define which eBPF programs are essential for an application's operations and specify -how these programs should be deployed across the cluster. - -## BpfProgram CRD - -The `BpfProgram` CRD is used internally by the `bpfman-deployment` to keep track of per-node `bpfman` state, -such as map pinpoints, and to report node-specific errors back to the user. +* `uprobeProgram` and `uprobeNsProgam` +* `xdpProgram` and `xdpNsProgram` + +There is also the `bpfApplication` and `bpfNsApplication` CRDs, which are +designed for managing eBPF programs at an application level within a Kubernetes cluster. +These CRD allows Kubernetes users to define which eBPF programs are essential for an application's operations +and specify how these programs should be deployed across the cluster. +With cluster scoped variant (`bpfApplication`), any variation of the cluster scoped +eBPF programs can be loaded. +With namespace scoped variant (`bpfNsApplication`), any variation of the namespace scoped +eBPF programs can be loaded. + +### BpfProgram and BpfNsProgram CRD + +The `BpfProgram` and `BpfNsProgram` CRDs are used internally by the bpfman-deployment to keep track of per +node bpfman state such as map pin points, and to report node specific errors back to the user. Kubernetes' users/controllers are only allowed to view these objects, NOT create or edit them. Applications wishing to use `bpfman` to deploy/manage their eBPF programs in Kubernetes will make use of this diff --git a/apis/v1alpha1/bpfapplication_types.go b/apis/v1alpha1/bpfApplication_types.go similarity index 100% rename from apis/v1alpha1/bpfapplication_types.go rename to apis/v1alpha1/bpfApplication_types.go diff --git a/apis/v1alpha1/bpfNsApplication_types.go b/apis/v1alpha1/bpfNsApplication_types.go new file mode 100644 index 000000000..acd78ba79 --- /dev/null +++ b/apis/v1alpha1/bpfNsApplication_types.go @@ -0,0 +1,97 @@ +/* +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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// BpfNsApplicationProgram defines the desired state of BpfApplication +// +union +// +kubebuilder:validation:XValidation:rule="has(self.type) && self.type == 'XDP' ? has(self.xdp) : !has(self.xdp)",message="xdp configuration is required when type is XDP, and forbidden otherwise" +// +kubebuilder:validation:XValidation:rule="has(self.type) && self.type == 'TC' ? has(self.tc) : !has(self.tc)",message="tc configuration is required when type is TC, and forbidden otherwise" +// +kubebuilder:validation:XValidation:rule="has(self.type) && self.type == 'TCX' ? has(self.tcx) : !has(self.tcx)",message="tcx configuration is required when type is TCX, and forbidden otherwise" +// +kubebuilder:validation:XValidation:rule="has(self.type) && self.type == 'Uprobe' ? has(self.uprobe) : !has(self.uprobe)",message="uprobe configuration is required when type is Uprobe, and forbidden otherwise" +// +kubebuilder:validation:XValidation:rule="has(self.type) && self.type == 'Uretprobe' ? has(self.uretprobe) : !has(self.uretprobe)",message="uretprobe configuration is required when type is Uretprobe, and forbidden otherwise" +type BpfNsApplicationProgram struct { + // Type specifies the bpf program type + // +unionDiscriminator + // +kubebuilder:validation:Required + // +kubebuilder:validation:Enum:="XDP";"TC";"TCX";"Uprobe";"Uretprobe" + Type EBPFProgType `json:"type,omitempty"` + + // xdp defines the desired state of the application's XdpNsPrograms. + // +unionMember + // +optional + XDP *XdpNsProgramInfo `json:"xdp,omitempty"` + + // tc defines the desired state of the application's TcNsPrograms. + // +unionMember + // +optional + TC *TcNsProgramInfo `json:"tc,omitempty"` + + // tcx defines the desired state of the application's TcxNsPrograms. + // +unionMember + // +optional + TCX *TcxNsProgramInfo `json:"tcx,omitempty"` + + // uprobe defines the desired state of the application's UprobeNsPrograms. + // +unionMember + // +optional + Uprobe *UprobeNsProgramInfo `json:"uprobe,omitempty"` + + // uretprobe defines the desired state of the application's UretprobeNsPrograms. + // +unionMember + // +optional + Uretprobe *UprobeNsProgramInfo `json:"uretprobe,omitempty"` +} + +// BpfApplicationSpec defines the desired state of BpfApplication +type BpfNsApplicationSpec struct { + BpfAppCommon `json:",inline"` + + // Programs is a list of bpf programs supported for a specific application. + // It's possible that the application can selectively choose which program(s) + // to run from this list. + // +kubebuilder:validation:MinItems:=1 + Programs []BpfNsApplicationProgram `json:"programs,omitempty"` +} + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Namespaced + +// BpfNsApplication is the Schema for the bpfapplications API +// +kubebuilder:printcolumn:name="NodeSelector",type=string,JSONPath=`.spec.nodeselector` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +type BpfNsApplication struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BpfNsApplicationSpec `json:"spec,omitempty"` + Status BpfApplicationStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// BpfNsApplicationList contains a list of BpfNsApplications +type BpfNsApplicationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []BpfNsApplication `json:"items"` +} diff --git a/apis/v1alpha1/bpfNsProgram_types.go b/apis/v1alpha1/bpfNsProgram_types.go new file mode 100644 index 000000000..58688fa3d --- /dev/null +++ b/apis/v1alpha1/bpfNsProgram_types.go @@ -0,0 +1,79 @@ +/* +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. +*/ + +// All fields are required unless explicitly marked optional +// +kubebuilder:validation:Required +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1types "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// BpfNsProgram is the Schema for the Bpfnsprograms API +// +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.type` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +type BpfNsProgram struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BpfProgramSpec `json:"spec"` + // +optional + Status BpfProgramStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// BpfNsProgramList contains a list of BpfProgram +type BpfNsProgramList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []BpfNsProgram `json:"items"` +} + +func (bp BpfNsProgram) GetName() string { + return bp.Name +} + +func (bp BpfNsProgram) GetUID() metav1types.UID { + return bp.UID +} + +func (bp BpfNsProgram) GetAnnotations() map[string]string { + return bp.Annotations +} + +func (bp BpfNsProgram) GetLabels() map[string]string { + return bp.Labels +} + +func (bp BpfNsProgram) GetStatus() *BpfProgramStatus { + return &bp.Status +} + +func (bp BpfNsProgram) GetClientObject() client.Object { + return &bp +} + +func (bpl BpfNsProgramList) GetItems() []BpfNsProgram { + return bpl.Items +} diff --git a/apis/v1alpha1/bpfprogram_types.go b/apis/v1alpha1/bpfProgram_types.go similarity index 80% rename from apis/v1alpha1/bpfprogram_types.go rename to apis/v1alpha1/bpfProgram_types.go index 9fa52c644..9e841bd44 100644 --- a/apis/v1alpha1/bpfprogram_types.go +++ b/apis/v1alpha1/bpfProgram_types.go @@ -20,6 +20,8 @@ package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1types "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" ) // +genclient @@ -69,3 +71,31 @@ type BpfProgramList struct { metav1.ListMeta `json:"metadata,omitempty"` Items []BpfProgram `json:"items"` } + +func (bp BpfProgram) GetName() string { + return bp.Name +} + +func (bp BpfProgram) GetUID() metav1types.UID { + return bp.UID +} + +func (bp BpfProgram) GetAnnotations() map[string]string { + return bp.Annotations +} + +func (bp BpfProgram) GetLabels() map[string]string { + return bp.Labels +} + +func (bp BpfProgram) GetStatus() *BpfProgramStatus { + return &bp.Status +} + +func (bp BpfProgram) GetClientObject() client.Object { + return &bp +} + +func (bpl BpfProgramList) GetItems() []BpfProgram { + return bpl.Items +} diff --git a/apis/v1alpha1/kprobeProgram_types.go b/apis/v1alpha1/kprobeProgram_types.go index b427e9c44..796a81da7 100644 --- a/apis/v1alpha1/kprobeProgram_types.go +++ b/apis/v1alpha1/kprobeProgram_types.go @@ -71,10 +71,6 @@ type KprobeProgramInfo struct { // +optional // +kubebuilder:default:=false RetProbe bool `json:"retprobe"` - - // // Host PID of container to attach the uprobe in. (Not supported yet by bpfman.) - // // +optional - // ContainerPid string `json:"containerpid"` } // KprobeProgramStatus defines the observed state of KprobeProgram diff --git a/apis/v1alpha1/shared_types.go b/apis/v1alpha1/shared_types.go index c31bd8eb4..7a6dca35f 100644 --- a/apis/v1alpha1/shared_types.go +++ b/apis/v1alpha1/shared_types.go @@ -53,6 +53,20 @@ type ContainerSelector struct { ContainerNames *[]string `json:"containernames,omitempty"` } +// ContainerNsSelector identifies a set of containers. It is different from ContainerSelector +// in that "Namespace" was removed. Namespace scoped programs can only attach to the namespace +// they are created in, so namespace at this level doesn't apply. +type ContainerNsSelector struct { + // Target pods. This field must be specified, to select all pods use + // standard metav1.LabelSelector semantics and make it empty. + Pods metav1.LabelSelector `json:"pods"` + + // Name(s) of container(s). If none are specified, all containers in the + // pod are selected. + // +optional + ContainerNames *[]string `json:"containernames,omitempty"` +} + // BpfProgramCommon defines the common attributes for all BPF programs type BpfProgramCommon struct { // BpfFunctionName is the name of the function that is the entry point for the BPF diff --git a/apis/v1alpha1/tcNsProgram_types.go b/apis/v1alpha1/tcNsProgram_types.go new file mode 100644 index 000000000..a8eca7372 --- /dev/null +++ b/apis/v1alpha1/tcNsProgram_types.go @@ -0,0 +1,90 @@ +/* +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. +*/ + +// All fields are required unless explicitly marked optional +// +kubebuilder:validation:Required +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Namespaced + +// TcNsProgram is the Schema for the TcNsProgram API +// +kubebuilder:printcolumn:name="BpfFunctionName",type=string,JSONPath=`.spec.bpffunctionname` +// +kubebuilder:printcolumn:name="NodeSelector",type=string,JSONPath=`.spec.nodeselector` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` +// +kubebuilder:printcolumn:name="Priority",type=string,JSONPath=`.spec.priority`,priority=1 +// +kubebuilder:printcolumn:name="Direction",type=string,JSONPath=`.spec.direction`,priority=1 +// +kubebuilder:printcolumn:name="InterfaceSelector",type=string,JSONPath=`.spec.interfaceselector`,priority=1 +// +kubebuilder:printcolumn:name="ProceedOn",type=string,JSONPath=`.spec.proceedon`,priority=1 +type TcNsProgram struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TcNsProgramSpec `json:"spec"` + // +optional + Status TcProgramStatus `json:"status,omitempty"` +} + +// TcNsProgramSpec defines the desired state of TcNsProgram +type TcNsProgramSpec struct { + TcNsProgramInfo `json:",inline"` + BpfAppCommon `json:",inline"` +} + +// TcNsProgramInfo defines the tc program details +type TcNsProgramInfo struct { + BpfProgramCommon `json:",inline"` + + // Selector to determine the network interface (or interfaces) + InterfaceSelector InterfaceSelector `json:"interfaceselector"` + + // Containers identifies the set of containers in which to attach the eBPF + // program. + Containers ContainerNsSelector `json:"containers"` + + // Priority specifies the priority of the tc program in relation to + // other programs of the same type with the same attach point. It is a value + // from 0 to 1000 where lower values have higher precedence. + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=1000 + Priority int32 `json:"priority"` + + // Direction specifies the direction of traffic the tc program should + // attach to for a given network device. + // +kubebuilder:validation:Enum=ingress;egress + Direction string `json:"direction"` + + // ProceedOn allows the user to call other tc programs in chain on this exit code. + // Multiple values are supported by repeating the parameter. + // +optional + // +kubebuilder:validation:MaxItems=11 + // +kubebuilder:default:={pipe,dispatcher_return} + ProceedOn []TcProceedOnValue `json:"proceedon"` +} + +// +kubebuilder:object:root=true +// TcNsProgramList contains a list of TcNsPrograms +type TcNsProgramList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []TcNsProgram `json:"items"` +} diff --git a/apis/v1alpha1/tcProgram_types.go b/apis/v1alpha1/tcProgram_types.go index 6db1481b7..519234214 100644 --- a/apis/v1alpha1/tcProgram_types.go +++ b/apis/v1alpha1/tcProgram_types.go @@ -61,7 +61,7 @@ type TcProgramInfo struct { // Selector to determine the network interface (or interfaces) InterfaceSelector InterfaceSelector `json:"interfaceselector"` - // Containers identifes the set of containers in which to attach the eBPF + // Containers identifies the set of containers in which to attach the eBPF // program. If Containers is not specified, the BPF program will be attached // in the root network namespace. // +optional diff --git a/apis/v1alpha1/tcxNsProgram_types.go b/apis/v1alpha1/tcxNsProgram_types.go new file mode 100644 index 000000000..61b8c8df3 --- /dev/null +++ b/apis/v1alpha1/tcxNsProgram_types.go @@ -0,0 +1,83 @@ +/* +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. +*/ + +// All fields are required unless explicitly marked optional +// +kubebuilder:validation:Required +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Namespaced + +// TcxNsProgram is the Schema for the TcxNsProgram API +// +kubebuilder:printcolumn:name="BpfFunctionName",type=string,JSONPath=`.spec.bpffunctionname` +// +kubebuilder:printcolumn:name="NodeSelector",type=string,JSONPath=`.spec.nodeselector` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` +// +kubebuilder:printcolumn:name="Direction",type=string,JSONPath=`.spec.direction`,priority=1 +// +kubebuilder:printcolumn:name="InterfaceSelector",type=string,JSONPath=`.spec.interfaceselector`,priority=1 +// +kubebuilder:printcolumn:name="Position",type=string,JSONPath=`.spec.position`,priority=1 +// +kubebuilder:printcolumn:name="Priority",type=string,JSONPath=`.spec.priority`,priority=1 +type TcxNsProgram struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TcxNsProgramSpec `json:"spec"` + // +optional + Status TcxProgramStatus `json:"status,omitempty"` +} + +// TcxNsProgramSpec defines the desired state of TcxNsProgram +type TcxNsProgramSpec struct { + TcxNsProgramInfo `json:",inline"` + BpfAppCommon `json:",inline"` +} + +// TcxNsProgramInfo defines the TCX Ns Program details +type TcxNsProgramInfo struct { + BpfProgramCommon `json:",inline"` + + // Selector to determine the network interface (or interfaces) + InterfaceSelector InterfaceSelector `json:"interfaceselector"` + + // Containers identifies the set of containers in which to attach the eBPF + // program. + Containers ContainerNsSelector `json:"containers"` + + // Direction specifies the direction of traffic the tcx program should + // attach to for a given network device. + // +kubebuilder:validation:Enum=ingress;egress + Direction string `json:"direction"` + + // Priority specifies the priority of the tc program in relation to + // other programs of the same type with the same attach point. It is a value + // from 0 to 1000 where lower values have higher precedence. + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=1000 + Priority int32 `json:"priority"` +} + +// +kubebuilder:object:root=true +// TcxNsProgramList contains a list of TcxNsPrograms +type TcxNsProgramList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []TcxNsProgram `json:"items"` +} diff --git a/apis/v1alpha1/tcxProgram_types.go b/apis/v1alpha1/tcxProgram_types.go index 6036ca8a4..e3de33c87 100644 --- a/apis/v1alpha1/tcxProgram_types.go +++ b/apis/v1alpha1/tcxProgram_types.go @@ -58,7 +58,7 @@ type TcxProgramInfo struct { // Selector to determine the network interface (or interfaces) InterfaceSelector InterfaceSelector `json:"interfaceselector"` - // Containers identifes the set of containers in which to attach the eBPF + // Containers identifies the set of containers in which to attach the eBPF // program. If Containers is not specified, the BPF program will be attached // in the root network namespace. // +optional @@ -77,7 +77,7 @@ type TcxProgramInfo struct { Priority int32 `json:"priority"` } -// TcxProgramStatus defines the observed state of TcProgram +// TcxProgramStatus defines the observed state of TcxProgram type TcxProgramStatus struct { BpfProgramStatusCommon `json:",inline"` } diff --git a/apis/v1alpha1/uprobeNsProgram_types.go b/apis/v1alpha1/uprobeNsProgram_types.go new file mode 100644 index 000000000..2b8dd21e0 --- /dev/null +++ b/apis/v1alpha1/uprobeNsProgram_types.go @@ -0,0 +1,95 @@ +/* +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. +*/ + +// All fields are required unless explicitly marked optional +// +kubebuilder:validation:Required +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Namespaced + +// UprobeNsProgram is the Schema for the UprobeNsPrograms API +// +kubebuilder:printcolumn:name="BpfFunctionName",type=string,JSONPath=`.spec.bpffunctionname` +// +kubebuilder:printcolumn:name="NodeSelector",type=string,JSONPath=`.spec.nodeselector` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` +// +kubebuilder:printcolumn:name="FunctionName",type=string,JSONPath=`.spec.func_name`,priority=1 +// +kubebuilder:printcolumn:name="Offset",type=integer,JSONPath=`.spec.offset`,priority=1 +// +kubebuilder:printcolumn:name="Target",type=string,JSONPath=`.spec.target`,priority=1 +// +kubebuilder:printcolumn:name="RetProbe",type=boolean,JSONPath=`.spec.retprobe`,priority=1 +// +kubebuilder:printcolumn:name="Pid",type=integer,JSONPath=`.spec.pid`,priority=1 +type UprobeNsProgram struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec UprobeNsProgramSpec `json:"spec"` + // +optional + Status UprobeProgramStatus `json:"status,omitempty"` +} + +// UprobeNsProgramSpec defines the desired state of UprobeProgram +type UprobeNsProgramSpec struct { + UprobeNsProgramInfo `json:",inline"` + BpfAppCommon `json:",inline"` +} + +// UprobeProgramInfo contains the information about the uprobe program +type UprobeNsProgramInfo struct { + BpfProgramCommon `json:",inline"` + + // Function to attach the uprobe to. + // +optional + FunctionName string `json:"func_name"` + + // Offset added to the address of the function for uprobe. + // +optional + // +kubebuilder:default:=0 + Offset uint64 `json:"offset"` + + // Library name or the absolute path to a binary or library. + Target string `json:"target"` + + // Whether the program is a uretprobe. Default is false + // +optional + // +kubebuilder:default:=false + RetProbe bool `json:"retprobe"` + + // Only execute uprobe for given process identification number (PID). If PID + // is not provided, uprobe executes for all PIDs. + // +optional + Pid int32 `json:"pid"` + + // Containers identifies the set of containers in which to attach the uprobe. + // If Containers is not specified, the uprobe will be attached in the + // bpfman-agent container. The ContainerNsSelector is very flexible and even + // allows the selection of all containers in a cluster. If an attempt is + // made to attach uprobes to too many containers, it can have a negative + // impact on on the cluster. + Containers ContainerNsSelector `json:"containers"` +} + +// +kubebuilder:object:root=true +// UprobeNsProgramList contains a list of UprobeNsPrograms +type UprobeNsProgramList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []UprobeNsProgram `json:"items"` +} diff --git a/apis/v1alpha1/uprobeProgram_types.go b/apis/v1alpha1/uprobeProgram_types.go index b98500aad..664401145 100644 --- a/apis/v1alpha1/uprobeProgram_types.go +++ b/apis/v1alpha1/uprobeProgram_types.go @@ -83,7 +83,7 @@ type UprobeProgramInfo struct { // +optional Pid int32 `json:"pid"` - // Containers identifes the set of containers in which to attach the uprobe. + // Containers identifies the set of containers in which to attach the uprobe. // If Containers is not specified, the uprobe will be attached in the // bpfman-agent container. The ContainerSelector is very flexible and even // allows the selection of all containers in a cluster. If an attempt is diff --git a/apis/v1alpha1/xdpNsProgram_types.go b/apis/v1alpha1/xdpNsProgram_types.go new file mode 100644 index 000000000..d0619c074 --- /dev/null +++ b/apis/v1alpha1/xdpNsProgram_types.go @@ -0,0 +1,83 @@ +/* +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. +*/ + +// All fields are required unless explicitly marked optional +// +kubebuilder:validation:Required +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Namespaced + +// XdpNsProgram is the Schema for the XdpNsPrograms API +// +kubebuilder:printcolumn:name="BpfFunctionName",type=string,JSONPath=`.spec.bpffunctionname` +// +kubebuilder:printcolumn:name="NodeSelector",type=string,JSONPath=`.spec.nodeselector` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` +// +kubebuilder:printcolumn:name="Priority",type=string,JSONPath=`.spec.priority`,priority=1 +// +kubebuilder:printcolumn:name="InterfaceSelector",type=string,JSONPath=`.spec.interfaceselector`,priority=1 +// +kubebuilder:printcolumn:name="ProceedOn",type=string,JSONPath=`.spec.proceedon`,priority=1 +type XdpNsProgram struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec XdpNsProgramSpec `json:"spec"` + // +optional + Status XdpProgramStatus `json:"status,omitempty"` +} + +// XdpNsProgramSpec defines the desired state of XdpNsProgram +type XdpNsProgramSpec struct { + XdpNsProgramInfo `json:",inline"` + BpfAppCommon `json:",inline"` +} + +// XdpNsProgramInfo defines the common fields for all XdpProgram types +type XdpNsProgramInfo struct { + BpfProgramCommon `json:",inline"` + // Selector to determine the network interface (or interfaces) + InterfaceSelector InterfaceSelector `json:"interfaceselector"` + + // Containers identifies the set of containers in which to attach the eBPF + // program. + Containers ContainerNsSelector `json:"containers"` + + // Priority specifies the priority of the bpf program in relation to + // other programs of the same type with the same attach point. It is a value + // from 0 to 1000 where lower values have higher precedence. + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=1000 + Priority int32 `json:"priority"` + + // ProceedOn allows the user to call other xdp programs in chain on this exit code. + // Multiple values are supported by repeating the parameter. + // +optional + // +kubebuilder:validation:MaxItems=6 + // +kubebuilder:default:={pass,dispatcher_return} + ProceedOn []XdpProceedOnValue `json:"proceedon"` +} + +// +kubebuilder:object:root=true +// XdpProgramList contains a list of XdpPrograms +type XdpNsProgramList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []XdpNsProgram `json:"items"` +} diff --git a/apis/v1alpha1/xdpProgram_types.go b/apis/v1alpha1/xdpProgram_types.go index 83f023989..f39c31981 100644 --- a/apis/v1alpha1/xdpProgram_types.go +++ b/apis/v1alpha1/xdpProgram_types.go @@ -59,7 +59,7 @@ type XdpProgramInfo struct { // Selector to determine the network interface (or interfaces) InterfaceSelector InterfaceSelector `json:"interfaceselector"` - // Containers identifes the set of containers in which to attach the eBPF + // Containers identifies the set of containers in which to attach the eBPF // program. If Containers is not specified, the BPF program will be attached // in the root network namespace. // +optional diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index 3954623d5..54af5b645 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -221,6 +221,187 @@ func (in *BpfApplicationStatus) DeepCopy() *BpfApplicationStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BpfNsApplication) DeepCopyInto(out *BpfNsApplication) { + *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 BpfNsApplication. +func (in *BpfNsApplication) DeepCopy() *BpfNsApplication { + if in == nil { + return nil + } + out := new(BpfNsApplication) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BpfNsApplication) 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 *BpfNsApplicationList) DeepCopyInto(out *BpfNsApplicationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]BpfNsApplication, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BpfNsApplicationList. +func (in *BpfNsApplicationList) DeepCopy() *BpfNsApplicationList { + if in == nil { + return nil + } + out := new(BpfNsApplicationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BpfNsApplicationList) 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 *BpfNsApplicationProgram) DeepCopyInto(out *BpfNsApplicationProgram) { + *out = *in + if in.XDP != nil { + in, out := &in.XDP, &out.XDP + *out = new(XdpNsProgramInfo) + (*in).DeepCopyInto(*out) + } + if in.TC != nil { + in, out := &in.TC, &out.TC + *out = new(TcNsProgramInfo) + (*in).DeepCopyInto(*out) + } + if in.TCX != nil { + in, out := &in.TCX, &out.TCX + *out = new(TcxNsProgramInfo) + (*in).DeepCopyInto(*out) + } + if in.Uprobe != nil { + in, out := &in.Uprobe, &out.Uprobe + *out = new(UprobeNsProgramInfo) + (*in).DeepCopyInto(*out) + } + if in.Uretprobe != nil { + in, out := &in.Uretprobe, &out.Uretprobe + *out = new(UprobeNsProgramInfo) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BpfNsApplicationProgram. +func (in *BpfNsApplicationProgram) DeepCopy() *BpfNsApplicationProgram { + if in == nil { + return nil + } + out := new(BpfNsApplicationProgram) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BpfNsApplicationSpec) DeepCopyInto(out *BpfNsApplicationSpec) { + *out = *in + in.BpfAppCommon.DeepCopyInto(&out.BpfAppCommon) + if in.Programs != nil { + in, out := &in.Programs, &out.Programs + *out = make([]BpfNsApplicationProgram, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BpfNsApplicationSpec. +func (in *BpfNsApplicationSpec) DeepCopy() *BpfNsApplicationSpec { + if in == nil { + return nil + } + out := new(BpfNsApplicationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BpfNsProgram) DeepCopyInto(out *BpfNsProgram) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BpfNsProgram. +func (in *BpfNsProgram) DeepCopy() *BpfNsProgram { + if in == nil { + return nil + } + out := new(BpfNsProgram) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BpfNsProgram) 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 *BpfNsProgramList) DeepCopyInto(out *BpfNsProgramList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]BpfNsProgram, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BpfNsProgramList. +func (in *BpfNsProgramList) DeepCopy() *BpfNsProgramList { + if in == nil { + return nil + } + out := new(BpfNsProgramList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BpfNsProgramList) 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 *BpfProgram) DeepCopyInto(out *BpfProgram) { *out = *in @@ -400,6 +581,31 @@ func (in *BytecodeSelector) DeepCopy() *BytecodeSelector { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerNsSelector) DeepCopyInto(out *ContainerNsSelector) { + *out = *in + in.Pods.DeepCopyInto(&out.Pods) + if in.ContainerNames != nil { + in, out := &in.ContainerNames, &out.ContainerNames + *out = new([]string) + if **in != nil { + in, out := *in, *out + *out = make([]string, len(*in)) + copy(*out, *in) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerNsSelector. +func (in *ContainerNsSelector) DeepCopy() *ContainerNsSelector { + if in == nil { + return nil + } + out := new(ContainerNsSelector) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ContainerSelector) DeepCopyInto(out *ContainerSelector) { *out = *in @@ -793,6 +999,105 @@ func (in *KprobeProgramStatus) DeepCopy() *KprobeProgramStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TcNsProgram) DeepCopyInto(out *TcNsProgram) { + *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 TcNsProgram. +func (in *TcNsProgram) DeepCopy() *TcNsProgram { + if in == nil { + return nil + } + out := new(TcNsProgram) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TcNsProgram) 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 *TcNsProgramInfo) DeepCopyInto(out *TcNsProgramInfo) { + *out = *in + in.BpfProgramCommon.DeepCopyInto(&out.BpfProgramCommon) + in.InterfaceSelector.DeepCopyInto(&out.InterfaceSelector) + in.Containers.DeepCopyInto(&out.Containers) + if in.ProceedOn != nil { + in, out := &in.ProceedOn, &out.ProceedOn + *out = make([]TcProceedOnValue, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcNsProgramInfo. +func (in *TcNsProgramInfo) DeepCopy() *TcNsProgramInfo { + if in == nil { + return nil + } + out := new(TcNsProgramInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TcNsProgramList) DeepCopyInto(out *TcNsProgramList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TcNsProgram, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcNsProgramList. +func (in *TcNsProgramList) DeepCopy() *TcNsProgramList { + if in == nil { + return nil + } + out := new(TcNsProgramList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TcNsProgramList) 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 *TcNsProgramSpec) DeepCopyInto(out *TcNsProgramSpec) { + *out = *in + in.TcNsProgramInfo.DeepCopyInto(&out.TcNsProgramInfo) + in.BpfAppCommon.DeepCopyInto(&out.BpfAppCommon) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcNsProgramSpec. +func (in *TcNsProgramSpec) DeepCopy() *TcNsProgramSpec { + if in == nil { + return nil + } + out := new(TcNsProgramSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TcProgram) DeepCopyInto(out *TcProgram) { *out = *in @@ -912,6 +1217,100 @@ func (in *TcProgramStatus) DeepCopy() *TcProgramStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TcxNsProgram) DeepCopyInto(out *TcxNsProgram) { + *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 TcxNsProgram. +func (in *TcxNsProgram) DeepCopy() *TcxNsProgram { + if in == nil { + return nil + } + out := new(TcxNsProgram) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TcxNsProgram) 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 *TcxNsProgramInfo) DeepCopyInto(out *TcxNsProgramInfo) { + *out = *in + in.BpfProgramCommon.DeepCopyInto(&out.BpfProgramCommon) + in.InterfaceSelector.DeepCopyInto(&out.InterfaceSelector) + in.Containers.DeepCopyInto(&out.Containers) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcxNsProgramInfo. +func (in *TcxNsProgramInfo) DeepCopy() *TcxNsProgramInfo { + if in == nil { + return nil + } + out := new(TcxNsProgramInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TcxNsProgramList) DeepCopyInto(out *TcxNsProgramList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TcxNsProgram, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcxNsProgramList. +func (in *TcxNsProgramList) DeepCopy() *TcxNsProgramList { + if in == nil { + return nil + } + out := new(TcxNsProgramList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TcxNsProgramList) 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 *TcxNsProgramSpec) DeepCopyInto(out *TcxNsProgramSpec) { + *out = *in + in.TcxNsProgramInfo.DeepCopyInto(&out.TcxNsProgramInfo) + in.BpfAppCommon.DeepCopyInto(&out.BpfAppCommon) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcxNsProgramSpec. +func (in *TcxNsProgramSpec) DeepCopy() *TcxNsProgramSpec { + if in == nil { + return nil + } + out := new(TcxNsProgramSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TcxProgram) DeepCopyInto(out *TcxProgram) { *out = *in @@ -1139,6 +1538,99 @@ func (in *TracepointProgramStatus) DeepCopy() *TracepointProgramStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UprobeNsProgram) DeepCopyInto(out *UprobeNsProgram) { + *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 UprobeNsProgram. +func (in *UprobeNsProgram) DeepCopy() *UprobeNsProgram { + if in == nil { + return nil + } + out := new(UprobeNsProgram) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UprobeNsProgram) 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 *UprobeNsProgramInfo) DeepCopyInto(out *UprobeNsProgramInfo) { + *out = *in + in.BpfProgramCommon.DeepCopyInto(&out.BpfProgramCommon) + in.Containers.DeepCopyInto(&out.Containers) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UprobeNsProgramInfo. +func (in *UprobeNsProgramInfo) DeepCopy() *UprobeNsProgramInfo { + if in == nil { + return nil + } + out := new(UprobeNsProgramInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UprobeNsProgramList) DeepCopyInto(out *UprobeNsProgramList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]UprobeNsProgram, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UprobeNsProgramList. +func (in *UprobeNsProgramList) DeepCopy() *UprobeNsProgramList { + if in == nil { + return nil + } + out := new(UprobeNsProgramList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UprobeNsProgramList) 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 *UprobeNsProgramSpec) DeepCopyInto(out *UprobeNsProgramSpec) { + *out = *in + in.UprobeNsProgramInfo.DeepCopyInto(&out.UprobeNsProgramInfo) + in.BpfAppCommon.DeepCopyInto(&out.BpfAppCommon) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UprobeNsProgramSpec. +func (in *UprobeNsProgramSpec) DeepCopy() *UprobeNsProgramSpec { + if in == nil { + return nil + } + out := new(UprobeNsProgramSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UprobeProgram) DeepCopyInto(out *UprobeProgram) { *out = *in @@ -1252,6 +1744,105 @@ func (in *UprobeProgramStatus) DeepCopy() *UprobeProgramStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *XdpNsProgram) DeepCopyInto(out *XdpNsProgram) { + *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 XdpNsProgram. +func (in *XdpNsProgram) DeepCopy() *XdpNsProgram { + if in == nil { + return nil + } + out := new(XdpNsProgram) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *XdpNsProgram) 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 *XdpNsProgramInfo) DeepCopyInto(out *XdpNsProgramInfo) { + *out = *in + in.BpfProgramCommon.DeepCopyInto(&out.BpfProgramCommon) + in.InterfaceSelector.DeepCopyInto(&out.InterfaceSelector) + in.Containers.DeepCopyInto(&out.Containers) + if in.ProceedOn != nil { + in, out := &in.ProceedOn, &out.ProceedOn + *out = make([]XdpProceedOnValue, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new XdpNsProgramInfo. +func (in *XdpNsProgramInfo) DeepCopy() *XdpNsProgramInfo { + if in == nil { + return nil + } + out := new(XdpNsProgramInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *XdpNsProgramList) DeepCopyInto(out *XdpNsProgramList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]XdpNsProgram, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new XdpNsProgramList. +func (in *XdpNsProgramList) DeepCopy() *XdpNsProgramList { + if in == nil { + return nil + } + out := new(XdpNsProgramList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *XdpNsProgramList) 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 *XdpNsProgramSpec) DeepCopyInto(out *XdpNsProgramSpec) { + *out = *in + in.XdpNsProgramInfo.DeepCopyInto(&out.XdpNsProgramInfo) + in.BpfAppCommon.DeepCopyInto(&out.BpfAppCommon) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new XdpNsProgramSpec. +func (in *XdpNsProgramSpec) DeepCopy() *XdpNsProgramSpec { + if in == nil { + return nil + } + out := new(XdpNsProgramSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *XdpProgram) DeepCopyInto(out *XdpProgram) { *out = *in diff --git a/apis/v1alpha1/zz_generated.register.go b/apis/v1alpha1/zz_generated.register.go index 5b55645af..569ccddcd 100644 --- a/apis/v1alpha1/zz_generated.register.go +++ b/apis/v1alpha1/zz_generated.register.go @@ -63,6 +63,10 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &BpfApplication{}, &BpfApplicationList{}, + &BpfNsApplication{}, + &BpfNsApplicationList{}, + &BpfNsProgram{}, + &BpfNsProgramList{}, &BpfProgram{}, &BpfProgramList{}, &FentryProgram{}, @@ -71,14 +75,22 @@ func addKnownTypes(scheme *runtime.Scheme) error { &FexitProgramList{}, &KprobeProgram{}, &KprobeProgramList{}, + &TcNsProgram{}, + &TcNsProgramList{}, &TcProgram{}, &TcProgramList{}, + &TcxNsProgram{}, + &TcxNsProgramList{}, &TcxProgram{}, &TcxProgramList{}, &TracepointProgram{}, &TracepointProgramList{}, + &UprobeNsProgram{}, + &UprobeNsProgramList{}, &UprobeProgram{}, &UprobeProgramList{}, + &XdpNsProgram{}, + &XdpNsProgramList{}, &XdpProgram{}, &XdpProgramList{}, ) diff --git a/bundle/manifests/bpfman-agent-role_rbac.authorization.k8s.io_v1_clusterrole.yaml b/bundle/manifests/bpfman-agent-role_rbac.authorization.k8s.io_v1_clusterrole.yaml index feb710832..40fc33a2c 100644 --- a/bundle/manifests/bpfman-agent-role_rbac.authorization.k8s.io_v1_clusterrole.yaml +++ b/bundle/manifests/bpfman-agent-role_rbac.authorization.k8s.io_v1_clusterrole.yaml @@ -18,6 +18,40 @@ rules: - bpfapplications/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -86,6 +120,14 @@ rules: - kprobeprograms/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - tcnsprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: @@ -100,6 +142,14 @@ rules: - tcprograms/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: @@ -128,6 +178,14 @@ rules: - tracepointprograms/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - uprobensprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: @@ -142,6 +200,14 @@ rules: - uprobeprograms/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: diff --git a/bundle/manifests/bpfman-agent-role_rbac.authorization.k8s.io_v1_role.yaml b/bundle/manifests/bpfman-agent-role_rbac.authorization.k8s.io_v1_role.yaml new file mode 100644 index 000000000..eecf32a61 --- /dev/null +++ b/bundle/manifests/bpfman-agent-role_rbac.authorization.k8s.io_v1_role.yaml @@ -0,0 +1,52 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + name: bpfman-agent-role +rules: +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - tcnsprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - uprobensprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms/finalizers + verbs: + - update diff --git a/bundle/manifests/bpfman-bpfnsprogram-editor-role_rbac.authorization.k8s.io_v1_role.yaml b/bundle/manifests/bpfman-bpfnsprogram-editor-role_rbac.authorization.k8s.io_v1_role.yaml new file mode 100644 index 000000000..6bd7cf7cd --- /dev/null +++ b/bundle/manifests/bpfman-bpfnsprogram-editor-role_rbac.authorization.k8s.io_v1_role.yaml @@ -0,0 +1,31 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: bpfman-operator + app.kubernetes.io/instance: bpfnsprogram-editor-role + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: role + app.kubernetes.io/part-of: bpfman-operator + name: bpfman-bpfnsprogram-editor-role +rules: +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms/status + verbs: + - get diff --git a/bundle/manifests/bpfman-bpfnsprogram-viewer-role_rbac.authorization.k8s.io_v1_role.yaml b/bundle/manifests/bpfman-bpfnsprogram-viewer-role_rbac.authorization.k8s.io_v1_role.yaml new file mode 100644 index 000000000..9d83ad2e5 --- /dev/null +++ b/bundle/manifests/bpfman-bpfnsprogram-viewer-role_rbac.authorization.k8s.io_v1_role.yaml @@ -0,0 +1,27 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: bpfman-operator + app.kubernetes.io/instance: bpfnsprogram-viewer-role + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: role + app.kubernetes.io/part-of: bpfman-operator + name: bpfman-bpfnsprogram-viewer-role +rules: +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms/status + verbs: + - get diff --git a/bundle/manifests/bpfman-operator-role_rbac.authorization.k8s.io_v1_role.yaml b/bundle/manifests/bpfman-operator-role_rbac.authorization.k8s.io_v1_role.yaml new file mode 100644 index 000000000..4dcbda1fc --- /dev/null +++ b/bundle/manifests/bpfman-operator-role_rbac.authorization.k8s.io_v1_role.yaml @@ -0,0 +1,32 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + name: bpfman-operator-role +rules: +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms/status + verbs: + - get + - patch + - update diff --git a/bundle/manifests/bpfman-operator.clusterserviceversion.yaml b/bundle/manifests/bpfman-operator.clusterserviceversion.yaml index 5e79d542d..bf1a4e6bc 100644 --- a/bundle/manifests/bpfman-operator.clusterserviceversion.yaml +++ b/bundle/manifests/bpfman-operator.clusterserviceversion.yaml @@ -95,6 +95,96 @@ metadata: ] } }, + { + "apiVersion": "bpfman.io/v1alpha1", + "kind": "BpfNsApplication", + "metadata": { + "labels": { + "app.kubernetes.io/name": "bpfnsapplication" + }, + "name": "bpfapplication-sample", + "namespace": "acme" + }, + "spec": { + "bytecode": { + "image": { + "url": "quay.io/bpfman-bytecode/go-app-counter:latest" + } + }, + "nodeselector": {}, + "programs": [ + { + "tc": { + "bpffunctionname": "stats", + "containers": { + "pods": { + "matchLabels": { + "app": "nginx" + } + } + }, + "direction": "ingress", + "interfaceselector": { + "primarynodeinterface": true + }, + "priority": 55 + }, + "type": "TC" + }, + { + "tcx": { + "bpffunctionname": "tcx_stats", + "containers": { + "pods": { + "matchLabels": { + "app": "nginx" + } + } + }, + "direction": "ingress", + "interfaceselector": { + "primarynodeinterface": true + }, + "priority": 500 + }, + "type": "TCX" + }, + { + "type": "Uprobe", + "uprobe": { + "bpffunctionname": "uprobe_counter", + "containers": { + "pods": { + "matchLabels": { + "app": "nginx" + } + } + }, + "func_name": "malloc", + "retprobe": false, + "target": "libc" + } + }, + { + "type": "XDP", + "xdp": { + "bpffunctionname": "xdp_stats", + "containers": { + "pods": { + "matchLabels": { + "app": "nginx" + } + } + }, + "interfaceselector": { + "primarynodeinterface": true + }, + "priority": 55 + } + } + ] + } + }, { "apiVersion": "bpfman.io/v1alpha1", "kind": "FentryProgram", @@ -168,6 +258,54 @@ metadata: "retprobe": false } }, + { + "apiVersion": "bpfman.io/v1alpha1", + "kind": "TcNsProgram", + "metadata": { + "labels": { + "app.kubernetes.io/name": "tcnsprogram" + }, + "name": "tc-containers", + "namespace": "acme" + }, + "spec": { + "bpffunctionname": "pass", + "bytecode": { + "image": { + "url": "quay.io/bpfman-bytecode/tc_pass:latest" + } + }, + "containers": { + "containernames": [ + "nginx" + ], + "pods": { + "matchLabels": { + "app": "nginx" + } + } + }, + "direction": "ingress", + "globaldata": { + "GLOBAL_u32": [ + 13, + 12, + 11, + 10 + ], + "GLOBAL_u8": [ + 1 + ] + }, + "interfaceselector": { + "interfaces": [ + "eth0" + ] + }, + "nodeselector": {}, + "priority": 0 + } + }, { "apiVersion": "bpfman.io/v1alpha1", "kind": "TcProgram", @@ -203,6 +341,89 @@ metadata: "priority": 0 } }, + { + "apiVersion": "bpfman.io/v1alpha1", + "kind": "TcxNsProgram", + "metadata": { + "labels": { + "app.kubernetes.io/name": "tcxnsprogram" + }, + "name": "tcx-containers", + "namespace": "acme" + }, + "spec": { + "bpffunctionname": "tcx_next", + "bytecode": { + "image": { + "url": "quay.io/bpfman-bytecode/tcx_test:latest" + } + }, + "containers": { + "containernames": [ + "nginx" + ], + "pods": { + "matchLabels": { + "app": "nginx" + } + } + }, + "direction": "ingress", + "globaldata": { + "GLOBAL_u32": [ + 13, + 12, + 11, + 10 + ], + "GLOBAL_u8": [ + 1 + ] + }, + "interfaceselector": { + "interfaces": [ + "eth0" + ] + }, + "nodeselector": {}, + "priority": 0 + } + }, + { + "apiVersion": "bpfman.io/v1alpha1", + "kind": "TcxProgram", + "metadata": { + "labels": { + "app.kubernetes.io/name": "tcxprogram" + }, + "name": "tcx-pass-all-nodes" + }, + "spec": { + "bpffunctionname": "tcx_pass", + "bytecode": { + "image": { + "url": "quay.io/bpfman-bytecode/tcx_test:latest" + } + }, + "direction": "ingress", + "globaldata": { + "GLOBAL_u32": [ + 13, + 12, + 11, + 10 + ], + "GLOBAL_u8": [ + 1 + ] + }, + "interfaceselector": { + "primarynodeinterface": true + }, + "nodeselector": {}, + "priority": 0 + } + }, { "apiVersion": "bpfman.io/v1alpha1", "kind": "TracepointProgram", @@ -236,6 +457,50 @@ metadata: "nodeselector": {} } }, + { + "apiVersion": "bpfman.io/v1alpha1", + "kind": "UprobeNsProgram", + "metadata": { + "labels": { + "app.kubernetes.io/name": "uprobensprogram" + }, + "name": "uprobe-example", + "namespace": "acme" + }, + "spec": { + "bpffunctionname": "my_uprobe", + "bytecode": { + "image": { + "url": "quay.io/bpfman-bytecode/uprobe:latest" + } + }, + "containers": { + "containernames": [ + "nginx" + ], + "pods": { + "matchLabels": { + "app": "nginx" + } + } + }, + "func_name": "syscall", + "globaldata": { + "GLOBAL_u32": [ + 13, + 12, + 11, + 10 + ], + "GLOBAL_u8": [ + 1 + ] + }, + "nodeselector": {}, + "retprobe": false, + "target": "libc" + } + }, { "apiVersion": "bpfman.io/v1alpha1", "kind": "UprobeProgram", @@ -269,6 +534,53 @@ metadata: "target": "libc" } }, + { + "apiVersion": "bpfman.io/v1alpha1", + "kind": "XdpNsProgram", + "metadata": { + "labels": { + "app.kubernetes.io/name": "xdpnsprogram" + }, + "name": "xdp-ns-pass-all-nodes", + "namespace": "acme" + }, + "spec": { + "bpffunctionname": "pass", + "bytecode": { + "image": { + "url": "quay.io/bpfman-bytecode/xdp_pass:latest" + } + }, + "containers": { + "containernames": [ + "nginx" + ], + "pods": { + "matchLabels": { + "app": "nginx" + } + } + }, + "globaldata": { + "GLOBAL_u32": [ + 13, + 12, + 11, + 10 + ], + "GLOBAL_u8": [ + 1 + ] + }, + "interfaceselector": { + "interfaces": [ + "eth0" + ] + }, + "nodeselector": {}, + "priority": 0 + } + }, { "apiVersion": "bpfman.io/v1alpha1", "kind": "XdpProgram", @@ -307,7 +619,7 @@ metadata: capabilities: Basic Install categories: OpenShift Optional containerImage: quay.io/bpfman/bpfman-operator:latest - createdAt: "2024-12-06T14:27:05Z" + createdAt: "2024-12-19T02:32:37Z" features.operators.openshift.io/cnf: "false" features.operators.openshift.io/cni: "false" features.operators.openshift.io/csi: "true" @@ -357,6 +669,16 @@ spec: kind: BpfApplication name: bpfapplications.bpfman.io version: v1alpha1 + - description: BpfNsApplication is the Schema for the BpfNsApplications API + displayName: Bpf Namespaced Application + kind: BpfNsApplication + name: bpfnsapplications.bpfman.io + version: v1alpha1 + - description: BpfNsProgram is the Schema for the BpfNsProgram API + displayName: Bpf Namespaced Program + kind: BpfNsProgram + name: bpfnsprograms.bpfman.io + version: v1alpha1 - description: BpfProgram is the Schema for the BpfProgram API displayName: Bpf Program kind: BpfProgram @@ -377,12 +699,24 @@ spec: kind: KprobeProgram name: kprobeprograms.bpfman.io version: v1alpha1 + - description: TcNsProgram is the Schema for the Tcnsprograms API + displayName: TC Namespaced Program + kind: TcNsProgram + name: tcnsprograms.bpfman.io + version: v1alpha1 - description: TcProgram is the Schema for the Tcprograms API - displayName: Tc Program + displayName: TC Program kind: TcProgram name: tcprograms.bpfman.io version: v1alpha1 - - kind: TcxProgram + - description: TcxNsProgram is the Schema for the Tcxnsprograms API + displayName: TCX Namespaced Program + kind: TcxNsProgram + name: tcxnsprograms.bpfman.io + version: v1alpha1 + - description: TcxProgram is the Schema for the Tcxprograms API + displayName: TCX Program + kind: TcxProgram name: tcxprograms.bpfman.io version: v1alpha1 - description: TracepointProgram is the Schema for the Tracepointprograms API @@ -390,11 +724,21 @@ spec: kind: TracepointProgram name: tracepointprograms.bpfman.io version: v1alpha1 + - description: UprobeNsProgram is the Schema for the Uprobensprograms API + displayName: Uprobe Namespaced Program + kind: UprobeNsProgram + name: uprobensprograms.bpfman.io + version: v1alpha1 - description: UprobeProgram is the Schema for the Uprobeprograms API displayName: Uprobe Program kind: UprobeProgram name: uprobeprograms.bpfman.io version: v1alpha1 + - description: XdpNsProgram is the Schema for the Xdpnsprograms API + displayName: Xdp Namespaced Program + kind: XdpNsProgram + name: xdpnsprograms.bpfman.io + version: v1alpha1 - description: XdpProgram is the Schema for the Xdpprograms API displayName: Xdp Program kind: XdpProgram @@ -413,9 +757,11 @@ spec: `warn`, `error`, and `fatal`, defaults to `info`\n- `bpfman.agent.log.level`: the log level for the bpfman-agent currently supports `info`, `debug`, and `trace` \n\nThe bpfman operator deploys eBPF programs via CRDs. The following CRDs are - currently available, \n\n- BpfApplication\n- XdpProgram\n- TcProgram\n - TracepointProgram\n- - KprobeProgram\n- UprobeProgram\n- FentryProgram\n- FexitProgram\n\n ## More information\n\nPlease - checkout the [bpfman community website](https://bpfman.io/) for more information." + currently available, \n\n- BpfApplication\n- FentryProgram\n- FexitProgram\n - + KprobeProgram\n- TcProgram\n- TcxProgram\n- TracepointProgram\n- UprobeProgram\n- + XdpProgram\n - BpfNsApplication\n- TcProgram\n- TcxNsProgram\n- UprobeNsProgram\n- + XdpNsProgram\n\n ## More information\n\nPlease checkout the [bpfman community + website](https://bpfman.io/) for more information." displayName: Bpfman Operator icon: - base64data: | @@ -759,6 +1105,40 @@ spec: - get - patch - update + - apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - bpfnsapplications/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - bpfnsapplications/status + verbs: + - get + - patch + - update + - apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: @@ -851,6 +1231,32 @@ spec: - get - patch - update + - apiGroups: + - bpfman.io + resources: + - tcnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - tcnsprograms/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - tcnsprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -877,6 +1283,32 @@ spec: - get - patch - update + - apiGroups: + - bpfman.io + resources: + - tcxnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - tcxnsprograms/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - tcxnsprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -929,6 +1361,32 @@ spec: - get - patch - update + - apiGroups: + - bpfman.io + resources: + - uprobensprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - uprobensprograms/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - uprobensprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -955,6 +1413,32 @@ spec: - get - patch - update + - apiGroups: + - bpfman.io + resources: + - xdpnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - xdpnsprograms/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - xdpnsprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -1167,6 +1651,144 @@ spec: verbs: - create - patch + - apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - bpfnsapplications/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - bpfnsapplications/status + verbs: + - get + - patch + - update + - apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - get + - list + - watch + - apiGroups: + - bpfman.io + resources: + - tcnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - tcnsprograms/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - tcnsprograms/status + verbs: + - get + - patch + - update + - apiGroups: + - bpfman.io + resources: + - tcxnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - tcxnsprograms/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - tcxnsprograms/status + verbs: + - get + - patch + - update + - apiGroups: + - bpfman.io + resources: + - uprobensprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - uprobensprograms/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - uprobensprograms/status + verbs: + - get + - patch + - update + - apiGroups: + - bpfman.io + resources: + - xdpnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - xdpnsprograms/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - xdpnsprograms/status + verbs: + - get + - patch + - update serviceAccountName: bpfman-operator strategy: deployment installModes: diff --git a/bundle/manifests/bpfman.io_bpfapplications.yaml b/bundle/manifests/bpfman.io_bpfapplications.yaml index cd8cc45cd..fde48711d 100644 --- a/bundle/manifests/bpfman.io_bpfapplications.yaml +++ b/bundle/manifests/bpfman.io_bpfapplications.yaml @@ -461,7 +461,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: @@ -652,7 +652,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: @@ -904,7 +904,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the uprobe. + Containers identifies the set of containers in which to attach the uprobe. If Containers is not specified, the uprobe will be attached in the bpfman-agent container. The ContainerSelector is very flexible and even allows the selection of all containers in a cluster. If an attempt is @@ -1062,7 +1062,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the uprobe. + Containers identifies the set of containers in which to attach the uprobe. If Containers is not specified, the uprobe will be attached in the bpfman-agent container. The ContainerSelector is very flexible and even allows the selection of all containers in a cluster. If an attempt is @@ -1220,7 +1220,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: diff --git a/bundle/manifests/bpfman.io_bpfnsapplications.yaml b/bundle/manifests/bpfman.io_bpfnsapplications.yaml new file mode 100644 index 000000000..9a355155b --- /dev/null +++ b/bundle/manifests/bpfman.io_bpfnsapplications.yaml @@ -0,0 +1,1125 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + creationTimestamp: null + name: bpfnsapplications.bpfman.io +spec: + group: bpfman.io + names: + kind: BpfNsApplication + listKind: BpfNsApplicationList + plural: bpfnsapplications + singular: bpfnsapplication + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: BpfNsApplication is the Schema for the bpfapplications 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: BpfApplicationSpec defines the desired state of BpfApplication + properties: + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + 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 + programs: + description: |- + Programs is a list of bpf programs supported for a specific application. + It's possible that the application can selectively choose which program(s) + to run from this list. + items: + description: BpfNsApplicationProgram defines the desired state of + BpfApplication + properties: + tc: + description: tc defines the desired state of the application's + TcNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + 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 + required: + - pods + type: object + direction: + description: |- + Direction specifies the direction of traffic the tc program should + attach to for a given network device. + enum: + - ingress + - egress + type: string + interfaceselector: + description: Selector to determine the network interface + (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface + on the node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + 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 + priority: + description: |- + Priority specifies the priority of the tc program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + proceedon: + default: + - pipe + - dispatcher_return + description: |- + ProceedOn allows the user to call other tc programs in chain on this exit code. + Multiple values are supported by repeating the parameter. + items: + enum: + - unspec + - ok + - reclassify + - shot + - pipe + - stolen + - queued + - repeat + - redirect + - trap + - dispatcher_return + type: string + maxItems: 11 + type: array + required: + - bpffunctionname + - containers + - direction + - interfaceselector + - priority + type: object + tcx: + description: tcx defines the desired state of the application's + TcxNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + 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 + required: + - pods + type: object + direction: + description: |- + Direction specifies the direction of traffic the tcx program should + attach to for a given network device. + enum: + - ingress + - egress + type: string + interfaceselector: + description: Selector to determine the network interface + (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface + on the node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + 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 + priority: + description: |- + Priority specifies the priority of the tc program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + required: + - bpffunctionname + - containers + - direction + - interfaceselector + - priority + type: object + type: + description: Type specifies the bpf program type + enum: + - XDP + - TC + - TCX + - Uprobe + - Uretprobe + type: string + uprobe: + description: uprobe defines the desired state of the application's + UprobeNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the uprobe. + If Containers is not specified, the uprobe will be attached in the + bpfman-agent container. The ContainerNsSelector is very flexible and even + allows the selection of all containers in a cluster. If an attempt is + made to attach uprobes to too many containers, it can have a negative + impact on on the cluster. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + 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 + required: + - pods + type: object + func_name: + description: Function to attach the uprobe to. + type: string + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + 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 + offset: + default: 0 + description: Offset added to the address of the function + for uprobe. + format: int64 + type: integer + pid: + description: |- + Only execute uprobe for given process identification number (PID). If PID + is not provided, uprobe executes for all PIDs. + format: int32 + type: integer + retprobe: + default: false + description: Whether the program is a uretprobe. Default + is false + type: boolean + target: + description: Library name or the absolute path to a binary + or library. + type: string + required: + - bpffunctionname + - containers + - target + type: object + uretprobe: + description: uretprobe defines the desired state of the application's + UretprobeNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the uprobe. + If Containers is not specified, the uprobe will be attached in the + bpfman-agent container. The ContainerNsSelector is very flexible and even + allows the selection of all containers in a cluster. If an attempt is + made to attach uprobes to too many containers, it can have a negative + impact on on the cluster. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + 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 + required: + - pods + type: object + func_name: + description: Function to attach the uprobe to. + type: string + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + 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 + offset: + default: 0 + description: Offset added to the address of the function + for uprobe. + format: int64 + type: integer + pid: + description: |- + Only execute uprobe for given process identification number (PID). If PID + is not provided, uprobe executes for all PIDs. + format: int32 + type: integer + retprobe: + default: false + description: Whether the program is a uretprobe. Default + is false + type: boolean + target: + description: Library name or the absolute path to a binary + or library. + type: string + required: + - bpffunctionname + - containers + - target + type: object + xdp: + description: xdp defines the desired state of the application's + XdpNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + 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 + required: + - pods + type: object + interfaceselector: + description: Selector to determine the network interface + (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface + on the node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + 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 + priority: + description: |- + Priority specifies the priority of the bpf program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + proceedon: + default: + - pass + - dispatcher_return + description: |- + ProceedOn allows the user to call other xdp programs in chain on this exit code. + Multiple values are supported by repeating the parameter. + items: + enum: + - aborted + - drop + - pass + - tx + - redirect + - dispatcher_return + type: string + maxItems: 6 + type: array + required: + - bpffunctionname + - containers + - interfaceselector + - priority + type: object + type: object + x-kubernetes-validations: + - message: xdp configuration is required when type is XDP, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''XDP'' ? has(self.xdp) + : !has(self.xdp)' + - message: tc configuration is required when type is TC, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''TC'' ? has(self.tc) : + !has(self.tc)' + - message: tcx configuration is required when type is TCX, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''TCX'' ? has(self.tcx) + : !has(self.tcx)' + - message: uprobe configuration is required when type is Uprobe, + and forbidden otherwise + rule: 'has(self.type) && self.type == ''Uprobe'' ? has(self.uprobe) + : !has(self.uprobe)' + - message: uretprobe configuration is required when type is Uretprobe, + and forbidden otherwise + rule: 'has(self.type) && self.type == ''Uretprobe'' ? has(self.uretprobe) + : !has(self.uretprobe)' + minItems: 1 + type: array + required: + - bytecode + - nodeselector + type: object + status: + description: BpfApplicationStatus defines the observed state of BpfApplication + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + 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 + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/bpfman.io_bpfnsprograms.yaml b/bundle/manifests/bpfman.io_bpfnsprograms.yaml new file mode 100644 index 000000000..606780fdd --- /dev/null +++ b/bundle/manifests/bpfman.io_bpfnsprograms.yaml @@ -0,0 +1,150 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + creationTimestamp: null + name: bpfnsprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: BpfNsProgram + listKind: BpfNsProgramList + plural: bpfnsprograms + singular: bpfnsprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.type + name: Type + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: BpfNsProgram is the Schema for the Bpfnsprograms 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: BpfProgramSpec defines the desired state of BpfProgram + properties: + type: + description: Type specifies the bpf program type + type: string + type: object + status: + description: |- + BpfProgramStatus defines the observed state of BpfProgram + TODO Make these a fixed set of metav1.Condition.types and metav1.Condition.reasons + properties: + conditions: + description: |- + Conditions houses the updates regarding the actual implementation of + the bpf program on the node + Known .status.conditions.type are: "Available", "Progressing", and "Degraded" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + 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 + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/bpfman.io_tcnsprograms.yaml b/bundle/manifests/bpfman.io_tcnsprograms.yaml new file mode 100644 index 000000000..f2602002c --- /dev/null +++ b/bundle/manifests/bpfman.io_tcnsprograms.yaml @@ -0,0 +1,447 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + creationTimestamp: null + name: tcnsprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: TcNsProgram + listKind: TcNsProgramList + plural: tcnsprograms + singular: tcnsprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bpffunctionname + name: BpfFunctionName + type: string + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .spec.priority + name: Priority + priority: 1 + type: string + - jsonPath: .spec.direction + name: Direction + priority: 1 + type: string + - jsonPath: .spec.interfaceselector + name: InterfaceSelector + priority: 1 + type: string + - jsonPath: .spec.proceedon + name: ProceedOn + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: TcNsProgram is the Schema for the TcNsProgram 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: TcNsProgramSpec defines the desired state of TcNsProgram + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + 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 + required: + - pods + type: object + direction: + description: |- + Direction specifies the direction of traffic the tc program should + attach to for a given network device. + enum: + - ingress + - egress + type: string + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + interfaceselector: + description: Selector to determine the network interface (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface on the + node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + 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 + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + 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 + priority: + description: |- + Priority specifies the priority of the tc program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + proceedon: + default: + - pipe + - dispatcher_return + description: |- + ProceedOn allows the user to call other tc programs in chain on this exit code. + Multiple values are supported by repeating the parameter. + items: + enum: + - unspec + - ok + - reclassify + - shot + - pipe + - stolen + - queued + - repeat + - redirect + - trap + - dispatcher_return + type: string + maxItems: 11 + type: array + required: + - bpffunctionname + - bytecode + - containers + - direction + - interfaceselector + - nodeselector + - priority + type: object + status: + description: TcProgramStatus defines the observed state of TcProgram + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + 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 + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/bpfman.io_tcprograms.yaml b/bundle/manifests/bpfman.io_tcprograms.yaml index db4073a6c..6bd329110 100644 --- a/bundle/manifests/bpfman.io_tcprograms.yaml +++ b/bundle/manifests/bpfman.io_tcprograms.yaml @@ -117,7 +117,7 @@ spec: type: object containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: diff --git a/bundle/manifests/bpfman.io_tcxnsprograms.yaml b/bundle/manifests/bpfman.io_tcxnsprograms.yaml new file mode 100644 index 000000000..947e5c32d --- /dev/null +++ b/bundle/manifests/bpfman.io_tcxnsprograms.yaml @@ -0,0 +1,424 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + creationTimestamp: null + name: tcxnsprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: TcxNsProgram + listKind: TcxNsProgramList + plural: tcxnsprograms + singular: tcxnsprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bpffunctionname + name: BpfFunctionName + type: string + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .spec.direction + name: Direction + priority: 1 + type: string + - jsonPath: .spec.interfaceselector + name: InterfaceSelector + priority: 1 + type: string + - jsonPath: .spec.position + name: Position + priority: 1 + type: string + - jsonPath: .spec.priority + name: Priority + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: TcxNsProgram is the Schema for the TcxNsProgram 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: TcxNsProgramSpec defines the desired state of TcxNsProgram + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + 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 + required: + - pods + type: object + direction: + description: |- + Direction specifies the direction of traffic the tcx program should + attach to for a given network device. + enum: + - ingress + - egress + type: string + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + interfaceselector: + description: Selector to determine the network interface (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface on the + node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + 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 + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + 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 + priority: + description: |- + Priority specifies the priority of the tc program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + required: + - bpffunctionname + - bytecode + - containers + - direction + - interfaceselector + - nodeselector + - priority + type: object + status: + description: TcxProgramStatus defines the observed state of TcxProgram + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + 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 + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/bpfman.io_tcxprograms.yaml b/bundle/manifests/bpfman.io_tcxprograms.yaml index f176c2f80..00ee3d933 100644 --- a/bundle/manifests/bpfman.io_tcxprograms.yaml +++ b/bundle/manifests/bpfman.io_tcxprograms.yaml @@ -117,7 +117,7 @@ spec: type: object containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: @@ -335,7 +335,7 @@ spec: - priority type: object status: - description: TcxProgramStatus defines the observed state of TcProgram + description: TcxProgramStatus defines the observed state of TcxProgram properties: conditions: description: |- diff --git a/bundle/manifests/bpfman.io_uprobensprograms.yaml b/bundle/manifests/bpfman.io_uprobensprograms.yaml new file mode 100644 index 000000000..f16f7f81c --- /dev/null +++ b/bundle/manifests/bpfman.io_uprobensprograms.yaml @@ -0,0 +1,417 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + creationTimestamp: null + name: uprobensprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: UprobeNsProgram + listKind: UprobeNsProgramList + plural: uprobensprograms + singular: uprobensprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bpffunctionname + name: BpfFunctionName + type: string + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .spec.func_name + name: FunctionName + priority: 1 + type: string + - jsonPath: .spec.offset + name: Offset + priority: 1 + type: integer + - jsonPath: .spec.target + name: Target + priority: 1 + type: string + - jsonPath: .spec.retprobe + name: RetProbe + priority: 1 + type: boolean + - jsonPath: .spec.pid + name: Pid + priority: 1 + type: integer + name: v1alpha1 + schema: + openAPIV3Schema: + description: UprobeNsProgram is the Schema for the UprobeNsPrograms 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: UprobeNsProgramSpec defines the desired state of UprobeProgram + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + containers: + description: |- + Containers identifies the set of containers in which to attach the uprobe. + If Containers is not specified, the uprobe will be attached in the + bpfman-agent container. The ContainerNsSelector is very flexible and even + allows the selection of all containers in a cluster. If an attempt is + made to attach uprobes to too many containers, it can have a negative + impact on on the cluster. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + 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 + required: + - pods + type: object + func_name: + description: Function to attach the uprobe to. + type: string + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + 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 + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + 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 + offset: + default: 0 + description: Offset added to the address of the function for uprobe. + format: int64 + type: integer + pid: + description: |- + Only execute uprobe for given process identification number (PID). If PID + is not provided, uprobe executes for all PIDs. + format: int32 + type: integer + retprobe: + default: false + description: Whether the program is a uretprobe. Default is false + type: boolean + target: + description: Library name or the absolute path to a binary or library. + type: string + required: + - bpffunctionname + - bytecode + - containers + - nodeselector + - target + type: object + status: + description: UprobeProgramStatus defines the observed state of UprobeProgram + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + 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 + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/bpfman.io_uprobeprograms.yaml b/bundle/manifests/bpfman.io_uprobeprograms.yaml index 376ccbe27..a3cdab8ad 100644 --- a/bundle/manifests/bpfman.io_uprobeprograms.yaml +++ b/bundle/manifests/bpfman.io_uprobeprograms.yaml @@ -121,7 +121,7 @@ spec: type: object containers: description: |- - Containers identifes the set of containers in which to attach the uprobe. + Containers identifies the set of containers in which to attach the uprobe. If Containers is not specified, the uprobe will be attached in the bpfman-agent container. The ContainerSelector is very flexible and even allows the selection of all containers in a cluster. If an attempt is diff --git a/bundle/manifests/bpfman.io_xdpnsprograms.yaml b/bundle/manifests/bpfman.io_xdpnsprograms.yaml new file mode 100644 index 000000000..1b72eba49 --- /dev/null +++ b/bundle/manifests/bpfman.io_xdpnsprograms.yaml @@ -0,0 +1,429 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + creationTimestamp: null + name: xdpnsprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: XdpNsProgram + listKind: XdpNsProgramList + plural: xdpnsprograms + singular: xdpnsprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bpffunctionname + name: BpfFunctionName + type: string + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .spec.priority + name: Priority + priority: 1 + type: string + - jsonPath: .spec.interfaceselector + name: InterfaceSelector + priority: 1 + type: string + - jsonPath: .spec.proceedon + name: ProceedOn + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: XdpNsProgram is the Schema for the XdpNsPrograms 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: XdpNsProgramSpec defines the desired state of XdpNsProgram + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + 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 + required: + - pods + type: object + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + interfaceselector: + description: Selector to determine the network interface (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface on the + node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + 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 + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + 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 + priority: + description: |- + Priority specifies the priority of the bpf program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + proceedon: + default: + - pass + - dispatcher_return + description: |- + ProceedOn allows the user to call other xdp programs in chain on this exit code. + Multiple values are supported by repeating the parameter. + items: + enum: + - aborted + - drop + - pass + - tx + - redirect + - dispatcher_return + type: string + maxItems: 6 + type: array + required: + - bpffunctionname + - bytecode + - containers + - interfaceselector + - nodeselector + - priority + type: object + status: + description: XdpProgramStatus defines the observed state of XdpProgram + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + 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 + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/bpfman.io_xdpprograms.yaml b/bundle/manifests/bpfman.io_xdpprograms.yaml index 31791544f..f55582862 100644 --- a/bundle/manifests/bpfman.io_xdpprograms.yaml +++ b/bundle/manifests/bpfman.io_xdpprograms.yaml @@ -113,7 +113,7 @@ spec: type: object containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: diff --git a/cmd/bpfman-agent/main.go b/cmd/bpfman-agent/main.go index 82da32bd3..daaca165c 100644 --- a/cmd/bpfman-agent/main.go +++ b/cmd/bpfman-agent/main.go @@ -145,7 +145,7 @@ func main() { os.Exit(1) } - common := bpfmanagent.ReconcilerCommon{ + common := bpfmanagent.ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), GrpcConn: conn, @@ -154,69 +154,120 @@ func main() { Containers: containerGetter, } - if err = (&bpfmanagent.XdpProgramReconciler{ + commonCluster := bpfmanagent.ClusterProgramReconciler{ ReconcilerCommon: common, + } + + commonNs := bpfmanagent.ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + GrpcConn: conn, + BpfmanClient: gobpfman.NewBpfmanClient(conn), + NodeName: nodeName, + Containers: containerGetter, + } + + commonNamespace := bpfmanagent.NamespaceProgramReconciler{ + ReconcilerCommon: commonNs, + } + + if err = (&bpfmanagent.XdpProgramReconciler{ + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create xdpProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanagent.TcProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create tcProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanagent.TcxProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create tcxProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanagent.TracepointProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create tracepointProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanagent.KprobeProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create kprobeProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanagent.UprobeProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create uprobeProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanagent.FentryProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create fentryProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanagent.FexitProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create fexitProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanagent.BpfApplicationReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create BpfApplicationProgram controller", "controller", "BpfProgram") os.Exit(1) } + if err = (&bpfmanagent.TcNsProgramReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create tcNsProgram controller", "controller", "BpfNsProgram") + os.Exit(1) + } + + if err = (&bpfmanagent.TcxNsProgramReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create tcxNsProgram controller", "controller", "BpfNsProgram") + os.Exit(1) + } + + if err = (&bpfmanagent.UprobeNsProgramReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create uprobeNsProgram controller", "controller", "BpfNsProgram") + os.Exit(1) + } + + if err = (&bpfmanagent.XdpNsProgramReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create xdpNsProgram controller", "controller", "BpfNsProgram") + os.Exit(1) + } + + if err = (&bpfmanagent.BpfNsApplicationReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create BpfNsApplicationProgram controller", "controller", "BpfNsProgram") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/cmd/bpfman-operator/main.go b/cmd/bpfman-operator/main.go index b6f2e86a2..1b933de6e 100644 --- a/cmd/bpfman-operator/main.go +++ b/cmd/bpfman-operator/main.go @@ -58,7 +58,7 @@ func init() { } // Returns true if the current platform is Openshift. -func isOpenshift(client discovery.DiscoveryInterface, cfg *rest.Config) (bool, error) { +func isOpenshift(client discovery.DiscoveryInterface, _ *rest.Config) (bool, error) { k8sVersion, err := client.ServerVersion() if err != nil { setupLog.Info("issue occurred while fetching ServerVersion") @@ -163,11 +163,24 @@ func main() { os.Exit(1) } - common := bpfmanoperator.ReconcilerCommon{ + common := bpfmanoperator.ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), } + commonCluster := bpfmanoperator.ClusterProgramReconciler{ + ReconcilerCommon: common, + } + + commonNs := bpfmanoperator.ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } + + commonNamespace := bpfmanoperator.NamespaceProgramReconciler{ + ReconcilerCommon: commonNs, + } + setupLog.Info("Discovering APIs") dc, err := discovery.NewDiscoveryClientForConfig(mgr.GetConfig()) if err != nil { @@ -183,7 +196,7 @@ func main() { } if err = (&bpfmanoperator.BpfmanConfigReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, BpfmanStandardDeployment: internal.BpfmanDaemonManifestPath, CsiDriverDeployment: internal.BpfmanCsiDriverPath, RestrictedSCC: internal.BpfmanRestrictedSCCPath, @@ -194,67 +207,102 @@ func main() { } if err = (&bpfmanoperator.XdpProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create xdpProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanoperator.TcProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create tcProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanoperator.TcxProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create tcxProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanoperator.TracepointProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create tracepointProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanoperator.KprobeProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create kprobeProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanoperator.UprobeProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create uprobeProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanoperator.FentryProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create fentryProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanoperator.FexitProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create fexitProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanoperator.BpfApplicationReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "BpfApplication") os.Exit(1) } + + if err = (&bpfmanoperator.TcNsProgramReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create tcProgram controller", "controller", "BpfNsProgram") + os.Exit(1) + } + + if err = (&bpfmanoperator.TcxNsProgramReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create tcxNsProgram controller", "controller", "BpfNsProgram") + os.Exit(1) + } + + if err = (&bpfmanoperator.UprobeNsProgramReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create uprobeNsProgram controller", "controller", "BpfNsProgram") + os.Exit(1) + } + + if err = (&bpfmanoperator.XdpNsProgramReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create xdpNsProgram controller", "controller", "BpfNsProgram") + os.Exit(1) + } + + if err = (&bpfmanoperator.BpfNsApplicationReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "BpfNsApplication") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/config/crd/bases/bpfman.io_bpfapplications.yaml b/config/crd/bases/bpfman.io_bpfapplications.yaml index cc775ec1a..da9ec0e06 100644 --- a/config/crd/bases/bpfman.io_bpfapplications.yaml +++ b/config/crd/bases/bpfman.io_bpfapplications.yaml @@ -461,7 +461,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: @@ -652,7 +652,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: @@ -904,7 +904,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the uprobe. + Containers identifies the set of containers in which to attach the uprobe. If Containers is not specified, the uprobe will be attached in the bpfman-agent container. The ContainerSelector is very flexible and even allows the selection of all containers in a cluster. If an attempt is @@ -1062,7 +1062,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the uprobe. + Containers identifies the set of containers in which to attach the uprobe. If Containers is not specified, the uprobe will be attached in the bpfman-agent container. The ContainerSelector is very flexible and even allows the selection of all containers in a cluster. If an attempt is @@ -1220,7 +1220,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: diff --git a/config/crd/bases/bpfman.io_bpfnsapplications.yaml b/config/crd/bases/bpfman.io_bpfnsapplications.yaml new file mode 100644 index 000000000..403eaf2e2 --- /dev/null +++ b/config/crd/bases/bpfman.io_bpfnsapplications.yaml @@ -0,0 +1,1119 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: bpfnsapplications.bpfman.io +spec: + group: bpfman.io + names: + kind: BpfNsApplication + listKind: BpfNsApplicationList + plural: bpfnsapplications + singular: bpfnsapplication + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: BpfNsApplication is the Schema for the bpfapplications 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: BpfApplicationSpec defines the desired state of BpfApplication + properties: + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + 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 + programs: + description: |- + Programs is a list of bpf programs supported for a specific application. + It's possible that the application can selectively choose which program(s) + to run from this list. + items: + description: BpfNsApplicationProgram defines the desired state of + BpfApplication + properties: + tc: + description: tc defines the desired state of the application's + TcNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + 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 + required: + - pods + type: object + direction: + description: |- + Direction specifies the direction of traffic the tc program should + attach to for a given network device. + enum: + - ingress + - egress + type: string + interfaceselector: + description: Selector to determine the network interface + (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface + on the node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + 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 + priority: + description: |- + Priority specifies the priority of the tc program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + proceedon: + default: + - pipe + - dispatcher_return + description: |- + ProceedOn allows the user to call other tc programs in chain on this exit code. + Multiple values are supported by repeating the parameter. + items: + enum: + - unspec + - ok + - reclassify + - shot + - pipe + - stolen + - queued + - repeat + - redirect + - trap + - dispatcher_return + type: string + maxItems: 11 + type: array + required: + - bpffunctionname + - containers + - direction + - interfaceselector + - priority + type: object + tcx: + description: tcx defines the desired state of the application's + TcxNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + 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 + required: + - pods + type: object + direction: + description: |- + Direction specifies the direction of traffic the tcx program should + attach to for a given network device. + enum: + - ingress + - egress + type: string + interfaceselector: + description: Selector to determine the network interface + (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface + on the node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + 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 + priority: + description: |- + Priority specifies the priority of the tc program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + required: + - bpffunctionname + - containers + - direction + - interfaceselector + - priority + type: object + type: + description: Type specifies the bpf program type + enum: + - XDP + - TC + - TCX + - Uprobe + - Uretprobe + type: string + uprobe: + description: uprobe defines the desired state of the application's + UprobeNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the uprobe. + If Containers is not specified, the uprobe will be attached in the + bpfman-agent container. The ContainerNsSelector is very flexible and even + allows the selection of all containers in a cluster. If an attempt is + made to attach uprobes to too many containers, it can have a negative + impact on on the cluster. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + 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 + required: + - pods + type: object + func_name: + description: Function to attach the uprobe to. + type: string + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + 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 + offset: + default: 0 + description: Offset added to the address of the function + for uprobe. + format: int64 + type: integer + pid: + description: |- + Only execute uprobe for given process identification number (PID). If PID + is not provided, uprobe executes for all PIDs. + format: int32 + type: integer + retprobe: + default: false + description: Whether the program is a uretprobe. Default + is false + type: boolean + target: + description: Library name or the absolute path to a binary + or library. + type: string + required: + - bpffunctionname + - containers + - target + type: object + uretprobe: + description: uretprobe defines the desired state of the application's + UretprobeNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the uprobe. + If Containers is not specified, the uprobe will be attached in the + bpfman-agent container. The ContainerNsSelector is very flexible and even + allows the selection of all containers in a cluster. If an attempt is + made to attach uprobes to too many containers, it can have a negative + impact on on the cluster. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + 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 + required: + - pods + type: object + func_name: + description: Function to attach the uprobe to. + type: string + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + 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 + offset: + default: 0 + description: Offset added to the address of the function + for uprobe. + format: int64 + type: integer + pid: + description: |- + Only execute uprobe for given process identification number (PID). If PID + is not provided, uprobe executes for all PIDs. + format: int32 + type: integer + retprobe: + default: false + description: Whether the program is a uretprobe. Default + is false + type: boolean + target: + description: Library name or the absolute path to a binary + or library. + type: string + required: + - bpffunctionname + - containers + - target + type: object + xdp: + description: xdp defines the desired state of the application's + XdpNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + 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 + required: + - pods + type: object + interfaceselector: + description: Selector to determine the network interface + (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface + on the node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + 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 + priority: + description: |- + Priority specifies the priority of the bpf program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + proceedon: + default: + - pass + - dispatcher_return + description: |- + ProceedOn allows the user to call other xdp programs in chain on this exit code. + Multiple values are supported by repeating the parameter. + items: + enum: + - aborted + - drop + - pass + - tx + - redirect + - dispatcher_return + type: string + maxItems: 6 + type: array + required: + - bpffunctionname + - containers + - interfaceselector + - priority + type: object + type: object + x-kubernetes-validations: + - message: xdp configuration is required when type is XDP, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''XDP'' ? has(self.xdp) + : !has(self.xdp)' + - message: tc configuration is required when type is TC, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''TC'' ? has(self.tc) : + !has(self.tc)' + - message: tcx configuration is required when type is TCX, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''TCX'' ? has(self.tcx) + : !has(self.tcx)' + - message: uprobe configuration is required when type is Uprobe, + and forbidden otherwise + rule: 'has(self.type) && self.type == ''Uprobe'' ? has(self.uprobe) + : !has(self.uprobe)' + - message: uretprobe configuration is required when type is Uretprobe, + and forbidden otherwise + rule: 'has(self.type) && self.type == ''Uretprobe'' ? has(self.uretprobe) + : !has(self.uretprobe)' + minItems: 1 + type: array + required: + - bytecode + - nodeselector + type: object + status: + description: BpfApplicationStatus defines the observed state of BpfApplication + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + 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 + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/bpfman.io_bpfnsprograms.yaml b/config/crd/bases/bpfman.io_bpfnsprograms.yaml new file mode 100644 index 000000000..3cd7faabb --- /dev/null +++ b/config/crd/bases/bpfman.io_bpfnsprograms.yaml @@ -0,0 +1,144 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: bpfnsprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: BpfNsProgram + listKind: BpfNsProgramList + plural: bpfnsprograms + singular: bpfnsprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.type + name: Type + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: BpfNsProgram is the Schema for the Bpfnsprograms 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: BpfProgramSpec defines the desired state of BpfProgram + properties: + type: + description: Type specifies the bpf program type + type: string + type: object + status: + description: |- + BpfProgramStatus defines the observed state of BpfProgram + TODO Make these a fixed set of metav1.Condition.types and metav1.Condition.reasons + properties: + conditions: + description: |- + Conditions houses the updates regarding the actual implementation of + the bpf program on the node + Known .status.conditions.type are: "Available", "Progressing", and "Degraded" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + 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 + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/bpfman.io_tcnsprograms.yaml b/config/crd/bases/bpfman.io_tcnsprograms.yaml new file mode 100644 index 000000000..12a633c45 --- /dev/null +++ b/config/crd/bases/bpfman.io_tcnsprograms.yaml @@ -0,0 +1,441 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: tcnsprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: TcNsProgram + listKind: TcNsProgramList + plural: tcnsprograms + singular: tcnsprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bpffunctionname + name: BpfFunctionName + type: string + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .spec.priority + name: Priority + priority: 1 + type: string + - jsonPath: .spec.direction + name: Direction + priority: 1 + type: string + - jsonPath: .spec.interfaceselector + name: InterfaceSelector + priority: 1 + type: string + - jsonPath: .spec.proceedon + name: ProceedOn + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: TcNsProgram is the Schema for the TcNsProgram 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: TcNsProgramSpec defines the desired state of TcNsProgram + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + 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 + required: + - pods + type: object + direction: + description: |- + Direction specifies the direction of traffic the tc program should + attach to for a given network device. + enum: + - ingress + - egress + type: string + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + interfaceselector: + description: Selector to determine the network interface (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface on the + node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + 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 + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + 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 + priority: + description: |- + Priority specifies the priority of the tc program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + proceedon: + default: + - pipe + - dispatcher_return + description: |- + ProceedOn allows the user to call other tc programs in chain on this exit code. + Multiple values are supported by repeating the parameter. + items: + enum: + - unspec + - ok + - reclassify + - shot + - pipe + - stolen + - queued + - repeat + - redirect + - trap + - dispatcher_return + type: string + maxItems: 11 + type: array + required: + - bpffunctionname + - bytecode + - containers + - direction + - interfaceselector + - nodeselector + - priority + type: object + status: + description: TcProgramStatus defines the observed state of TcProgram + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + 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 + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/bpfman.io_tcprograms.yaml b/config/crd/bases/bpfman.io_tcprograms.yaml index 713629be2..fa56278c8 100644 --- a/config/crd/bases/bpfman.io_tcprograms.yaml +++ b/config/crd/bases/bpfman.io_tcprograms.yaml @@ -117,7 +117,7 @@ spec: type: object containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: diff --git a/config/crd/bases/bpfman.io_tcxnsprograms.yaml b/config/crd/bases/bpfman.io_tcxnsprograms.yaml new file mode 100644 index 000000000..f7508f18e --- /dev/null +++ b/config/crd/bases/bpfman.io_tcxnsprograms.yaml @@ -0,0 +1,418 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: tcxnsprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: TcxNsProgram + listKind: TcxNsProgramList + plural: tcxnsprograms + singular: tcxnsprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bpffunctionname + name: BpfFunctionName + type: string + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .spec.direction + name: Direction + priority: 1 + type: string + - jsonPath: .spec.interfaceselector + name: InterfaceSelector + priority: 1 + type: string + - jsonPath: .spec.position + name: Position + priority: 1 + type: string + - jsonPath: .spec.priority + name: Priority + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: TcxNsProgram is the Schema for the TcxNsProgram 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: TcxNsProgramSpec defines the desired state of TcxNsProgram + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + 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 + required: + - pods + type: object + direction: + description: |- + Direction specifies the direction of traffic the tcx program should + attach to for a given network device. + enum: + - ingress + - egress + type: string + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + interfaceselector: + description: Selector to determine the network interface (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface on the + node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + 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 + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + 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 + priority: + description: |- + Priority specifies the priority of the tc program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + required: + - bpffunctionname + - bytecode + - containers + - direction + - interfaceselector + - nodeselector + - priority + type: object + status: + description: TcxProgramStatus defines the observed state of TcxProgram + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + 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 + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/bpfman.io_tcxprograms.yaml b/config/crd/bases/bpfman.io_tcxprograms.yaml index d0eca6137..bf97f8e41 100644 --- a/config/crd/bases/bpfman.io_tcxprograms.yaml +++ b/config/crd/bases/bpfman.io_tcxprograms.yaml @@ -117,7 +117,7 @@ spec: type: object containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: @@ -335,7 +335,7 @@ spec: - priority type: object status: - description: TcxProgramStatus defines the observed state of TcProgram + description: TcxProgramStatus defines the observed state of TcxProgram properties: conditions: description: |- diff --git a/config/crd/bases/bpfman.io_uprobensprograms.yaml b/config/crd/bases/bpfman.io_uprobensprograms.yaml new file mode 100644 index 000000000..45be66c7d --- /dev/null +++ b/config/crd/bases/bpfman.io_uprobensprograms.yaml @@ -0,0 +1,411 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: uprobensprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: UprobeNsProgram + listKind: UprobeNsProgramList + plural: uprobensprograms + singular: uprobensprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bpffunctionname + name: BpfFunctionName + type: string + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .spec.func_name + name: FunctionName + priority: 1 + type: string + - jsonPath: .spec.offset + name: Offset + priority: 1 + type: integer + - jsonPath: .spec.target + name: Target + priority: 1 + type: string + - jsonPath: .spec.retprobe + name: RetProbe + priority: 1 + type: boolean + - jsonPath: .spec.pid + name: Pid + priority: 1 + type: integer + name: v1alpha1 + schema: + openAPIV3Schema: + description: UprobeNsProgram is the Schema for the UprobeNsPrograms 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: UprobeNsProgramSpec defines the desired state of UprobeProgram + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + containers: + description: |- + Containers identifies the set of containers in which to attach the uprobe. + If Containers is not specified, the uprobe will be attached in the + bpfman-agent container. The ContainerNsSelector is very flexible and even + allows the selection of all containers in a cluster. If an attempt is + made to attach uprobes to too many containers, it can have a negative + impact on on the cluster. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + 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 + required: + - pods + type: object + func_name: + description: Function to attach the uprobe to. + type: string + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + 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 + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + 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 + offset: + default: 0 + description: Offset added to the address of the function for uprobe. + format: int64 + type: integer + pid: + description: |- + Only execute uprobe for given process identification number (PID). If PID + is not provided, uprobe executes for all PIDs. + format: int32 + type: integer + retprobe: + default: false + description: Whether the program is a uretprobe. Default is false + type: boolean + target: + description: Library name or the absolute path to a binary or library. + type: string + required: + - bpffunctionname + - bytecode + - containers + - nodeselector + - target + type: object + status: + description: UprobeProgramStatus defines the observed state of UprobeProgram + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + 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 + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/bpfman.io_uprobeprograms.yaml b/config/crd/bases/bpfman.io_uprobeprograms.yaml index 356c58008..26e3daf9b 100644 --- a/config/crd/bases/bpfman.io_uprobeprograms.yaml +++ b/config/crd/bases/bpfman.io_uprobeprograms.yaml @@ -121,7 +121,7 @@ spec: type: object containers: description: |- - Containers identifes the set of containers in which to attach the uprobe. + Containers identifies the set of containers in which to attach the uprobe. If Containers is not specified, the uprobe will be attached in the bpfman-agent container. The ContainerSelector is very flexible and even allows the selection of all containers in a cluster. If an attempt is diff --git a/config/crd/bases/bpfman.io_xdpnsprograms.yaml b/config/crd/bases/bpfman.io_xdpnsprograms.yaml new file mode 100644 index 000000000..971eb5949 --- /dev/null +++ b/config/crd/bases/bpfman.io_xdpnsprograms.yaml @@ -0,0 +1,423 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: xdpnsprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: XdpNsProgram + listKind: XdpNsProgramList + plural: xdpnsprograms + singular: xdpnsprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bpffunctionname + name: BpfFunctionName + type: string + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .spec.priority + name: Priority + priority: 1 + type: string + - jsonPath: .spec.interfaceselector + name: InterfaceSelector + priority: 1 + type: string + - jsonPath: .spec.proceedon + name: ProceedOn + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: XdpNsProgram is the Schema for the XdpNsPrograms 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: XdpNsProgramSpec defines the desired state of XdpNsProgram + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + 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 + required: + - pods + type: object + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + interfaceselector: + description: Selector to determine the network interface (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface on the + node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + 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 + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + 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 + priority: + description: |- + Priority specifies the priority of the bpf program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + proceedon: + default: + - pass + - dispatcher_return + description: |- + ProceedOn allows the user to call other xdp programs in chain on this exit code. + Multiple values are supported by repeating the parameter. + items: + enum: + - aborted + - drop + - pass + - tx + - redirect + - dispatcher_return + type: string + maxItems: 6 + type: array + required: + - bpffunctionname + - bytecode + - containers + - interfaceselector + - nodeselector + - priority + type: object + status: + description: XdpProgramStatus defines the observed state of XdpProgram + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + 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 + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/bpfman.io_xdpprograms.yaml b/config/crd/bases/bpfman.io_xdpprograms.yaml index 3e15190b0..38b1cafb6 100644 --- a/config/crd/bases/bpfman.io_xdpprograms.yaml +++ b/config/crd/bases/bpfman.io_xdpprograms.yaml @@ -113,7 +113,7 @@ spec: type: object containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 41da2bb50..2b77f35da 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -12,6 +12,12 @@ resources: - bases/bpfman.io_fentryprograms.yaml - bases/bpfman.io_fexitprograms.yaml - bases/bpfman.io_bpfapplications.yaml + - bases/bpfman.io_bpfnsprograms.yaml + - bases/bpfman.io_tcnsprograms.yaml + - bases/bpfman.io_tcxnsprograms.yaml + - bases/bpfman.io_xdpnsprograms.yaml + - bases/bpfman.io_uprobensprograms.yaml + - bases/bpfman.io_bpfnsapplications.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -19,6 +25,7 @@ patchesStrategicMerge: # patches here are for enabling the conversion webhook for each CRD #- patches/webhook_in_bpfprograms.yaml #- patches/webhook_in_tcprograms.yaml +#- patches/webhook_in_tcxprograms.yaml #- patches/webhook_in_xdpprograms.yaml #- patches/webhook_in_tracepointprograms.yaml #- patches/webhook_in_kprobeprograms.yaml @@ -26,12 +33,19 @@ patchesStrategicMerge: #- patches/webhook_in_fentryprograms.yaml #- patches/webhook_in_fexitprograms.yaml #- patches/webhook_in_bpfapplications.yaml +#- patches/webhook_in_bpfnsprograms.yaml +#- patches/webhook_in_tcnsprograms.yaml +#- patches/webhook_in_tcxnsprograms.yaml +#- patches/webhook_in_xdpnsprograms.yaml +#- patches/webhook_in_uprobensprograms.yaml +#- patches/webhook_in_bpfnsapplications.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. # patches here are for enabling the CA injection for each CRD #- patches/cainjection_in_bpfprograms.yaml #- patches/cainjection_in_tcprograms.yaml +#- patches/cainjection_in_tcxprograms.yaml #- patches/cainjection_in_xdpprograms.yaml #- patches/cainjection_in_tracepointprograms.yaml #- patches/cainjection_in_kprobeprograms.yaml @@ -39,6 +53,12 @@ patchesStrategicMerge: #- patches/cainjection_in_fentryprograms.yaml #- patches/cainjection_in_fexitrograms.yaml #- patches/cainjection_in_bpfapplications.yaml +#- patches/cainjection_in_bpfnsprograms.yaml +#- patches/cainjection_in_tcnsprograms.yaml +#- patches/cainjection_in_tcxnsprograms.yaml +#- patches/cainjection_in_xdpnsprograms.yaml +#- patches/cainjection_in_uprobensprograms.yaml +#- patches/cainjection_in_bpfnsapplications.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_bpfnsapplications.yaml b/config/crd/patches/cainjection_in_bpfnsapplications.yaml new file mode 100644 index 000000000..148537d83 --- /dev/null +++ b/config/crd/patches/cainjection_in_bpfnsapplications.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: bpfnsapplications.bpfman.io diff --git a/config/crd/patches/cainjection_in_bpfnsprograms.yaml b/config/crd/patches/cainjection_in_bpfnsprograms.yaml new file mode 100644 index 000000000..dddcc8ab4 --- /dev/null +++ b/config/crd/patches/cainjection_in_bpfnsprograms.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: bpfnsprograms.bpfman.io diff --git a/config/crd/patches/cainjection_in_tcnsprograms.yaml b/config/crd/patches/cainjection_in_tcnsprograms.yaml new file mode 100644 index 000000000..3fef5f5bf --- /dev/null +++ b/config/crd/patches/cainjection_in_tcnsprograms.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: tcnsprograms.bpfman.io diff --git a/config/crd/patches/cainjection_in_tcxnsprograms.yaml b/config/crd/patches/cainjection_in_tcxnsprograms.yaml new file mode 100644 index 000000000..95d7e86fc --- /dev/null +++ b/config/crd/patches/cainjection_in_tcxnsprograms.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: tcxnsprograms.bpfman.io diff --git a/config/crd/patches/cainjection_in_tcxprograms.yaml b/config/crd/patches/cainjection_in_tcxprograms.yaml new file mode 100644 index 000000000..e6e38a06f --- /dev/null +++ b/config/crd/patches/cainjection_in_tcxprograms.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: tcxprograms.bpfman.io diff --git a/config/crd/patches/cainjection_in_uprobensprograms.yaml b/config/crd/patches/cainjection_in_uprobensprograms.yaml new file mode 100644 index 000000000..4db3df4ed --- /dev/null +++ b/config/crd/patches/cainjection_in_uprobensprograms.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: uprobensprograms.bpfman.io diff --git a/config/crd/patches/cainjection_in_xdpnsprograms.yaml b/config/crd/patches/cainjection_in_xdpnsprograms.yaml new file mode 100644 index 000000000..9a44e43d8 --- /dev/null +++ b/config/crd/patches/cainjection_in_xdpnsprograms.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: xdpnsprograms.bpfman.io diff --git a/config/crd/patches/webhook_in_bpfnsapplications.yaml b/config/crd/patches/webhook_in_bpfnsapplications.yaml new file mode 100644 index 000000000..5329072fc --- /dev/null +++ b/config/crd/patches/webhook_in_bpfnsapplications.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: bpfnsapplications.bpfman.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_bpfnsprograms.yaml b/config/crd/patches/webhook_in_bpfnsprograms.yaml new file mode 100644 index 000000000..264ad53a6 --- /dev/null +++ b/config/crd/patches/webhook_in_bpfnsprograms.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: bpfnsprograms.bpfman.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_tcnsprograms.yaml b/config/crd/patches/webhook_in_tcnsprograms.yaml new file mode 100644 index 000000000..47cc1ef09 --- /dev/null +++ b/config/crd/patches/webhook_in_tcnsprograms.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: tcnsprograms.bpfman.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_tcxnsprograms.yaml b/config/crd/patches/webhook_in_tcxnsprograms.yaml new file mode 100644 index 000000000..b71fb1760 --- /dev/null +++ b/config/crd/patches/webhook_in_tcxnsprograms.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: tcxnsprograms.bpfman.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_tcxprograms.yaml b/config/crd/patches/webhook_in_tcxprograms.yaml new file mode 100644 index 000000000..f85eeee16 --- /dev/null +++ b/config/crd/patches/webhook_in_tcxprograms.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: tcxprograms.bpfman.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_uprobensprograms.yaml b/config/crd/patches/webhook_in_uprobensprograms.yaml new file mode 100644 index 000000000..9c6e1c68c --- /dev/null +++ b/config/crd/patches/webhook_in_uprobensprograms.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: uprobensprograms.bpfman.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_xdpnsprograms.yaml b/config/crd/patches/webhook_in_xdpnsprograms.yaml new file mode 100644 index 000000000..c4d2aba26 --- /dev/null +++ b/config/crd/patches/webhook_in_xdpnsprograms.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: xdpnsprograms.bpfman.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/manifests/bases/bpfman-operator.clusterserviceversion.yaml b/config/manifests/bases/bpfman-operator.clusterserviceversion.yaml index 255546b5c..84f4de3fd 100644 --- a/config/manifests/bases/bpfman-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/bpfman-operator.clusterserviceversion.yaml @@ -52,21 +52,51 @@ spec: kind: BpfApplication name: bpfapplications.bpfman.io version: v1alpha1 + - description: BpfNsApplication is the Schema for the BpfNsApplications API + displayName: Bpf Namespaced Application + kind: BpfNsApplication + name: bpfnsapplications.bpfman.io + version: v1alpha1 - description: BpfProgram is the Schema for the BpfProgram API displayName: Bpf Program kind: BpfProgram name: bpfprograms.bpfman.io version: v1alpha1 + - description: BpfNsProgram is the Schema for the BpfNsProgram API + displayName: Bpf Namespaced Program + kind: BpfNsProgram + name: bpfnsprograms.bpfman.io + version: v1alpha1 - description: XdpProgram is the Schema for the Xdpprograms API displayName: Xdp Program kind: XdpProgram name: xdpprograms.bpfman.io version: v1alpha1 + - description: XdpNsProgram is the Schema for the Xdpnsprograms API + displayName: Xdp Namespaced Program + kind: XdpNsProgram + name: xdpnsprograms.bpfman.io + version: v1alpha1 - description: TcProgram is the Schema for the Tcprograms API - displayName: Tc Program + displayName: TC Program kind: TcProgram name: tcprograms.bpfman.io version: v1alpha1 + - description: TcNsProgram is the Schema for the Tcnsprograms API + displayName: TC Namespaced Program + kind: TcNsProgram + name: tcnsprograms.bpfman.io + version: v1alpha1 + - description: TcxProgram is the Schema for the Tcxprograms API + displayName: TCX Program + kind: TcxProgram + name: tcxprograms.bpfman.io + version: v1alpha1 + - description: TcxNsProgram is the Schema for the Tcxnsprograms API + displayName: TCX Namespaced Program + kind: TcxNsProgram + name: tcxnsprograms.bpfman.io + version: v1alpha1 - description: TracepointProgram is the Schema for the Tracepointprograms API displayName: Tracepoint Program kind: TracepointProgram @@ -82,6 +112,11 @@ spec: kind: UprobeProgram name: uprobeprograms.bpfman.io version: v1alpha1 + - description: UprobeNsProgram is the Schema for the Uprobensprograms API + displayName: Uprobe Namespaced Program + kind: UprobeNsProgram + name: uprobensprograms.bpfman.io + version: v1alpha1 - description: FentryProgram is the Schema for the Fentryprograms API displayName: Fentry Program kind: FentryProgram @@ -106,8 +141,9 @@ spec: supports `debug`, `info`, `warn`, `error`, and `fatal`, defaults to `info`\n- `bpfman.agent.log.level`: the log level for the bpfman-agent currently supports `info`, `debug`, and `trace` \n\nThe bpfman operator deploys eBPF programs via CRDs. - The following CRDs are currently available, \n\n- BpfApplication\n- XdpProgram\n- TcProgram\n - - TracepointProgram\n- KprobeProgram\n- UprobeProgram\n- FentryProgram\n- FexitProgram\n\n + The following CRDs are currently available, \n\n- BpfApplication\n- FentryProgram\n- FexitProgram\n + - KprobeProgram\n- TcProgram\n- TcxProgram\n- TracepointProgram\n- UprobeProgram\n- XdpProgram\n + - BpfNsApplication\n- TcProgram\n- TcxNsProgram\n- UprobeNsProgram\n- XdpNsProgram\n\n ## More information\n\nPlease checkout the [bpfman community website](https://bpfman.io/) for more information." displayName: Bpfman Operator diff --git a/config/rbac/bpfman-agent/role.yaml b/config/rbac/bpfman-agent/role.yaml index da51037c7..a54843bc4 100644 --- a/config/rbac/bpfman-agent/role.yaml +++ b/config/rbac/bpfman-agent/role.yaml @@ -18,6 +18,40 @@ rules: - bpfapplications/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -86,6 +120,14 @@ rules: - kprobeprograms/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - tcnsprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: @@ -100,6 +142,14 @@ rules: - tcprograms/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: @@ -128,6 +178,14 @@ rules: - tracepointprograms/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - uprobensprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: @@ -142,6 +200,14 @@ rules: - uprobeprograms/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: @@ -178,3 +244,56 @@ rules: - secrets verbs: - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: agent-role + namespace: bpfman +rules: +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - tcnsprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - uprobensprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms/finalizers + verbs: + - update diff --git a/config/rbac/bpfman-operator/role.yaml b/config/rbac/bpfman-operator/role.yaml index 5e0913a4c..91bc8b962 100644 --- a/config/rbac/bpfman-operator/role.yaml +++ b/config/rbac/bpfman-operator/role.yaml @@ -42,6 +42,40 @@ rules: - get - patch - update +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications/status + verbs: + - get + - patch + - update +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: @@ -134,6 +168,32 @@ rules: - get - patch - update +- apiGroups: + - bpfman.io + resources: + - tcnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - tcnsprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - tcnsprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -160,6 +220,32 @@ rules: - get - patch - update +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -212,6 +298,32 @@ rules: - get - patch - update +- apiGroups: + - bpfman.io + resources: + - uprobensprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - uprobensprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - uprobensprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -238,6 +350,32 @@ rules: - get - patch - update +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -301,3 +439,148 @@ rules: - get - list - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: operator-role + namespace: bpfman +rules: +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications/status + verbs: + - get + - patch + - update +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - tcnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - tcnsprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - tcnsprograms/status + verbs: + - get + - patch + - update +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms/status + verbs: + - get + - patch + - update +- apiGroups: + - bpfman.io + resources: + - uprobensprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - uprobensprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - uprobensprograms/status + verbs: + - get + - patch + - update +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms/status + verbs: + - get + - patch + - update diff --git a/config/rbac/bpfnsapplication_editor_role.yaml b/config/rbac/bpfnsapplication_editor_role.yaml new file mode 100644 index 000000000..cee4124aa --- /dev/null +++ b/config/rbac/bpfnsapplication_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit bpfapplications. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/name: role + app.kubernetes.io/instance: bpfnsapplication-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: bpfman-operator + app.kubernetes.io/part-of: bpfman-operator + app.kubernetes.io/managed-by: kustomize + name: bpfnsapplication-editor-role +rules: +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications/status + verbs: + - get diff --git a/config/rbac/bpfnsapplication_viewer_role.yaml b/config/rbac/bpfnsapplication_viewer_role.yaml new file mode 100644 index 000000000..4ae59969f --- /dev/null +++ b/config/rbac/bpfnsapplication_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view bpfapplications. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/name: role + app.kubernetes.io/instance: bpfnsapplication-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: bpfman-operator + app.kubernetes.io/part-of: bpfman-operator + app.kubernetes.io/managed-by: kustomize + name: bpfnsapplication-viewer-role +rules: +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications/status + verbs: + - get diff --git a/config/rbac/bpfnsprogram_editor_role.yaml b/config/rbac/bpfnsprogram_editor_role.yaml new file mode 100644 index 000000000..973a04b0d --- /dev/null +++ b/config/rbac/bpfnsprogram_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit bpfprograms. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/name: role + app.kubernetes.io/instance: bpfnsprogram-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: bpfman-operator + app.kubernetes.io/part-of: bpfman-operator + app.kubernetes.io/managed-by: kustomize + name: bpfnsprogram-editor-role +rules: + - apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - bpfnsprograms/status + verbs: + - get diff --git a/config/rbac/bpfnsprogram_viewer_role.yaml b/config/rbac/bpfnsprogram_viewer_role.yaml new file mode 100644 index 000000000..1cd2ba154 --- /dev/null +++ b/config/rbac/bpfnsprogram_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view bpfprograms. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/name: role + app.kubernetes.io/instance: bpfnsprogram-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: bpfman-operator + app.kubernetes.io/part-of: bpfman-operator + app.kubernetes.io/managed-by: kustomize + name: bpfnsprogram-viewer-role +rules: + - apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - get + - list + - watch + - apiGroups: + - bpfman.io + resources: + - bpfnsprograms/status + verbs: + - get diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index ad78ce73a..2d6028e1d 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -12,6 +12,8 @@ resources: - leader_election_role_binding.yaml - bpfprogram_editor_role.yaml - bpfprogram_viewer_role.yaml + - bpfnsprogram_editor_role.yaml + - bpfnsprogram_viewer_role.yaml # Comment the following 4 lines if you want to disable # the auth proxy (https://github.com/brancz/kube-rbac-proxy) # which protects your /metrics endpoint. diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml index a1791f6f9..874a081e9 100644 --- a/config/rbac/role_binding.yaml +++ b/config/rbac/role_binding.yaml @@ -38,6 +38,26 @@ subjects: --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding +metadata: + labels: + app.kubernetes.io/name: rolebinding + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: bpfman-operator + app.kubernetes.io/part-of: bpfman-operator + app.kubernetes.io/managed-by: kustomize + name: operator-rolebinding + namespace: bpfman +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: bpfman-operator-role +subjects: + - kind: ServiceAccount + name: operator + namespace: system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding metadata: labels: app.kubernetes.io/name: rolebinding diff --git a/config/samples/bpfman.io_v1alpha1_bpfnsapplication.yaml b/config/samples/bpfman.io_v1alpha1_bpfnsapplication.yaml new file mode 100644 index 000000000..16260b119 --- /dev/null +++ b/config/samples/bpfman.io_v1alpha1_bpfnsapplication.yaml @@ -0,0 +1,56 @@ +apiVersion: bpfman.io/v1alpha1 +kind: BpfNsApplication +metadata: + labels: + app.kubernetes.io/name: bpfnsapplication + name: bpfapplication-sample + namespace: acme +spec: + # Select all nodes + nodeselector: {} + bytecode: + image: + url: quay.io/bpfman-bytecode/go-app-counter:latest + programs: + - type: TC + tc: + bpffunctionname: stats + interfaceselector: + primarynodeinterface: true + priority: 55 + direction: ingress + containers: + pods: + matchLabels: + app: nginx + - type: TCX + tcx: + bpffunctionname: tcx_stats + interfaceselector: + primarynodeinterface: true + priority: 500 + direction: ingress + containers: + pods: + matchLabels: + app: nginx + - type: Uprobe + uprobe: + bpffunctionname: uprobe_counter + func_name: malloc + target: libc + retprobe: false + containers: + pods: + matchLabels: + app: nginx + - type: XDP + xdp: + bpffunctionname: xdp_stats + interfaceselector: + primarynodeinterface: true + priority: 55 + containers: + pods: + matchLabels: + app: nginx diff --git a/config/samples/bpfman.io_v1alpha1_tc_pass_tcnsprogram.yaml b/config/samples/bpfman.io_v1alpha1_tc_pass_tcnsprogram.yaml new file mode 100644 index 000000000..b0ae0fed4 --- /dev/null +++ b/config/samples/bpfman.io_v1alpha1_tc_pass_tcnsprogram.yaml @@ -0,0 +1,33 @@ +apiVersion: bpfman.io/v1alpha1 +kind: TcNsProgram +metadata: + labels: + app.kubernetes.io/name: tcnsprogram + name: tc-containers + namespace: acme +spec: + bpffunctionname: pass + # Select all nodes + nodeselector: {} + interfaceselector: + interfaces: + - eth0 + priority: 0 + direction: ingress + bytecode: + image: + url: quay.io/bpfman-bytecode/tc_pass:latest + globaldata: + GLOBAL_u8: + - 0x01 + GLOBAL_u32: + - 0x0D + - 0x0C + - 0x0B + - 0x0A + containers: + pods: + matchLabels: + app: nginx + containernames: + - nginx diff --git a/config/samples/bpfman.io_v1alpha1_tcx_pass_tcxnsprogram.yaml b/config/samples/bpfman.io_v1alpha1_tcx_pass_tcxnsprogram.yaml new file mode 100644 index 000000000..5f8628cd5 --- /dev/null +++ b/config/samples/bpfman.io_v1alpha1_tcx_pass_tcxnsprogram.yaml @@ -0,0 +1,33 @@ +apiVersion: bpfman.io/v1alpha1 +kind: TcxNsProgram +metadata: + labels: + app.kubernetes.io/name: tcxnsprogram + name: tcx-containers + namespace: acme +spec: + bpffunctionname: tcx_next + # Select all nodes + nodeselector: {} + interfaceselector: + interfaces: + - eth0 + priority: 0 + direction: ingress + bytecode: + image: + url: quay.io/bpfman-bytecode/tcx_test:latest + globaldata: + GLOBAL_u8: + - 0x01 + GLOBAL_u32: + - 0x0D + - 0x0C + - 0x0B + - 0x0A + containers: + pods: + matchLabels: + app: nginx + containernames: + - nginx diff --git a/config/samples/bpfman.io_v1alpha1_uprobe_uprobensprogram.yaml b/config/samples/bpfman.io_v1alpha1_uprobe_uprobensprogram.yaml new file mode 100644 index 000000000..5d66ec4d1 --- /dev/null +++ b/config/samples/bpfman.io_v1alpha1_uprobe_uprobensprogram.yaml @@ -0,0 +1,33 @@ +apiVersion: bpfman.io/v1alpha1 +kind: UprobeNsProgram +metadata: + labels: + app.kubernetes.io/name: uprobensprogram + name: uprobe-example + namespace: acme +spec: + # Select all nodes + nodeselector: {} + bpffunctionname: my_uprobe + func_name: syscall + # offset: 0 # optional offset w/in function + target: libc + retprobe: false + # pid: 0 # optional pid to execute uprobe for + bytecode: + image: + url: quay.io/bpfman-bytecode/uprobe:latest + globaldata: + GLOBAL_u8: + - 0x01 + GLOBAL_u32: + - 0x0D + - 0x0C + - 0x0B + - 0x0A + containers: + pods: + matchLabels: + app: nginx + containernames: + - nginx diff --git a/config/samples/bpfman.io_v1alpha1_xdp_pass_xdpnsprogram.yaml b/config/samples/bpfman.io_v1alpha1_xdp_pass_xdpnsprogram.yaml new file mode 100644 index 000000000..ecec2eda6 --- /dev/null +++ b/config/samples/bpfman.io_v1alpha1_xdp_pass_xdpnsprogram.yaml @@ -0,0 +1,32 @@ +apiVersion: bpfman.io/v1alpha1 +kind: XdpNsProgram +metadata: + labels: + app.kubernetes.io/name: xdpnsprogram + name: xdp-ns-pass-all-nodes + namespace: acme +spec: + bpffunctionname: pass + # Select all nodes + nodeselector: {} + interfaceselector: + interfaces: + - eth0 + priority: 0 + bytecode: + image: + url: quay.io/bpfman-bytecode/xdp_pass:latest + globaldata: + GLOBAL_u8: + - 0x01 + GLOBAL_u32: + - 0x0D + - 0x0C + - 0x0B + - 0x0A + containers: + pods: + matchLabels: + app: nginx + containernames: + - nginx diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 4795266f4..c2508f5f2 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -1,11 +1,17 @@ ## Append samples you want in your CSV to this file as resources ## resources: - - bpfman.io_v1alpha1_xdp_pass_xdpprogram.yaml - - bpfman.io_v1alpha1_tracepoint_tracepointprogram.yaml - - bpfman.io_v1alpha1_tc_pass_tcprogram.yaml - - bpfman.io_v1alpha1_kprobe_kprobeprogram.yaml - - bpfman.io_v1alpha1_uprobe_uprobeprogram.yaml - bpfman.io_v1alpha1_fentry_fentryprogram.yaml - bpfman.io_v1alpha1_fexit_fexitprogram.yaml + - bpfman.io_v1alpha1_kprobe_kprobeprogram.yaml + - bpfman.io_v1alpha1_tc_pass_tcprogram.yaml + - bpfman.io_v1alpha1_tcx_pass_tcxprogram.yaml + - bpfman.io_v1alpha1_tracepoint_tracepointprogram.yaml + - bpfman.io_v1alpha1_uprobe_uprobeprogram.yaml + - bpfman.io_v1alpha1_xdp_pass_xdpprogram.yaml - bpfman.io_v1alpha1_bpfapplication.yaml + - bpfman.io_v1alpha1_tc_pass_tcnsprogram.yaml + - bpfman.io_v1alpha1_tcx_pass_tcxnsprogram.yaml + - bpfman.io_v1alpha1_uprobe_uprobensprogram.yaml + - bpfman.io_v1alpha1_xdp_pass_xdpnsprogram.yaml + - bpfman.io_v1alpha1_bpfnsapplication.yaml # +kubebuilder:scaffold:manifestskustomizesamples diff --git a/controllers/bpfman-agent/application-ns-program.go b/controllers/bpfman-agent/application-ns-program.go new file mode 100644 index 000000000..3d35dda33 --- /dev/null +++ b/controllers/bpfman-agent/application-ns-program.go @@ -0,0 +1,288 @@ +/* +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 bpfmanagent + +import ( + "context" + "fmt" + "strings" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman-operator/internal" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfnsapplications,verbs=get;list;watch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=bpfnsapplications,verbs=get;list;watch + +type BpfNsApplicationReconciler struct { + NamespaceProgramReconciler + currentApp *bpfmaniov1alpha1.BpfNsApplication + ourNode *v1.Node +} + +func (r *BpfNsApplicationReconciler) getRecType() string { + return internal.ApplicationString +} + +func (r *BpfNsApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // Initialize node and current program + r.currentApp = &bpfmaniov1alpha1.BpfNsApplication{} + r.ourNode = &v1.Node{} + r.Logger = ctrl.Log.WithName("application-ns") + r.appOwner = &bpfmaniov1alpha1.BpfNsApplication{} + r.finalizer = internal.BpfNsApplicationControllerFinalizer + r.recType = internal.ApplicationString + + r.Logger.Info("bpfman-agent enter: application-ns", "Namespace", req.Namespace, "Name", req.Name) + + // Lookup K8s node object for this bpfman-agent This should always succeed + if err := r.Get(ctx, types.NamespacedName{Namespace: v1.NamespaceAll, Name: r.NodeName}, r.ourNode); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfman-agent node %s : %v", + req.NamespacedName, err) + } + + appPrograms := &bpfmaniov1alpha1.BpfNsApplicationList{} + + opts := []client.ListOption{} + + if err := r.List(ctx, appPrograms, opts...); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting BpfNsApplicationPrograms for full reconcile %s : %v", + req.NamespacedName, err) + } + + if len(appPrograms.Items) == 0 { + r.Logger.Info("BpfNsApplicationController found no application Programs") + return ctrl.Result{Requeue: false}, nil + } + + var res ctrl.Result + var err error + var complete bool + var lastRec bpfmanReconciler[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList] + + buildProgramName := func( + app bpfmaniov1alpha1.BpfNsApplication, + prog bpfmaniov1alpha1.BpfNsApplicationProgram) string { + return app.Name + "-" + strings.ToLower(string(prog.Type)) + } + + for i, a := range appPrograms.Items { + var appProgramMap = make(map[string]bool) + for j, p := range a.Spec.Programs { + switch p.Type { + case bpfmaniov1alpha1.ProgTypeUprobe, + bpfmaniov1alpha1.ProgTypeUretprobe: + appProgramId := fmt.Sprintf("%s-%s-%s", strings.ToLower(string(p.Type)), sanitize(p.Uprobe.FunctionName), p.Uprobe.BpfFunctionName) + uprobeProgram := bpfmaniov1alpha1.UprobeNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildProgramName(a, p), + Namespace: req.Namespace, + Labels: map[string]string{internal.AppProgramId: appProgramId}}, + Spec: bpfmaniov1alpha1.UprobeNsProgramSpec{ + UprobeNsProgramInfo: *p.Uprobe, + BpfAppCommon: a.Spec.BpfAppCommon, + }, + } + rec := &UprobeNsProgramReconciler{ + NamespaceProgramReconciler: r.NamespaceProgramReconciler, + currentUprobeNsProgram: &uprobeProgram, + ourNode: r.ourNode, + } + rec.appOwner = &a + uprobeObjects := []client.Object{&uprobeProgram} + appProgramMap[appProgramId] = true + // Reconcile UprobeNsProgram or UretprobeNsProgram. + complete, res, err = r.reconcileCommon(ctx, rec, uprobeObjects) + lastRec = rec + + case bpfmaniov1alpha1.ProgTypeTC: + _, ifErr := getInterfaces(&p.TC.InterfaceSelector, r.ourNode) + if ifErr != nil { + r.Logger.Error(ifErr, "failed to get interfaces for TC NS Program", + "app program name", a.Name, "program index", j) + continue + } + appProgramId := fmt.Sprintf("%s-%s-%s", strings.ToLower(string(p.Type)), p.TC.Direction, p.TC.BpfFunctionName) + tcProgram := bpfmaniov1alpha1.TcNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildProgramName(a, p), + Namespace: req.Namespace, + Labels: map[string]string{internal.AppProgramId: appProgramId}}, + Spec: bpfmaniov1alpha1.TcNsProgramSpec{ + TcNsProgramInfo: *p.TC, + BpfAppCommon: a.Spec.BpfAppCommon, + }, + } + rec := &TcNsProgramReconciler{ + NamespaceProgramReconciler: r.NamespaceProgramReconciler, + currentTcNsProgram: &tcProgram, + ourNode: r.ourNode, + } + rec.appOwner = &a + tcObjects := []client.Object{&tcProgram} + appProgramMap[appProgramId] = true + // Reconcile TcNsProgram. + complete, res, err = r.reconcileCommon(ctx, rec, tcObjects) + lastRec = rec + + case bpfmaniov1alpha1.ProgTypeTCX: + _, ifErr := getInterfaces(&p.TCX.InterfaceSelector, r.ourNode) + if ifErr != nil { + r.Logger.Error(ifErr, "failed to get interfaces for TCX Program", + "app program name", a.Name, "program index", j) + continue + } + appProgramId := fmt.Sprintf("%s-%s-%s", strings.ToLower(string(p.Type)), p.TCX.Direction, p.TCX.BpfFunctionName) + tcxProgram := bpfmaniov1alpha1.TcxNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildProgramName(a, p), + Namespace: req.Namespace, + Labels: map[string]string{internal.AppProgramId: appProgramId}}, + Spec: bpfmaniov1alpha1.TcxNsProgramSpec{ + TcxNsProgramInfo: *p.TCX, + BpfAppCommon: a.Spec.BpfAppCommon, + }, + } + rec := &TcxNsProgramReconciler{ + NamespaceProgramReconciler: r.NamespaceProgramReconciler, + currentTcxNsProgram: &tcxProgram, + ourNode: r.ourNode, + } + rec.appOwner = &a + tcxObjects := []client.Object{&tcxProgram} + appProgramMap[appProgramId] = true + // Reconcile TcxNsProgram. + complete, res, err = r.reconcileCommon(ctx, rec, tcxObjects) + lastRec = rec + + case bpfmaniov1alpha1.ProgTypeXDP: + _, ifErr := getInterfaces(&p.XDP.InterfaceSelector, r.ourNode) + if ifErr != nil { + r.Logger.Error(ifErr, "failed to get interfaces for XDP Program", + "app program name", a.Name, "program index", j) + continue + } + appProgramId := fmt.Sprintf("%s-%s", strings.ToLower(string(p.Type)), p.XDP.BpfFunctionName) + xdpProgram := bpfmaniov1alpha1.XdpNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildProgramName(a, p), + Namespace: req.Namespace, + Labels: map[string]string{internal.AppProgramId: appProgramId}}, + Spec: bpfmaniov1alpha1.XdpNsProgramSpec{ + XdpNsProgramInfo: *p.XDP, + BpfAppCommon: a.Spec.BpfAppCommon, + }, + } + rec := &XdpNsProgramReconciler{ + NamespaceProgramReconciler: r.NamespaceProgramReconciler, + currentXdpNsProgram: &xdpProgram, + ourNode: r.ourNode, + } + rec.appOwner = &a + xdpObjects := []client.Object{&xdpProgram} + appProgramMap[appProgramId] = true + // Reconcile XdpNsProgram. + complete, res, err = r.reconcileCommon(ctx, rec, xdpObjects) + lastRec = rec + + default: + r.Logger.Error(fmt.Errorf("unsupported bpf namespaced program type"), "unsupported bpf namespaced program type", "ProgType", p.Type) + // Skip this program and continue to the next one + continue + } + + r.Logger.V(1).Info("Reconcile Application", "Application", i, "Program", j, "Name", a.Name, + "type", p.Type, "Complete", complete, "Result", res, "Error", err) + + if complete { + // We've completed reconciling this program, continue to the next one + continue + } else { + return res, err + } + } + + if complete { + bpfPrograms := &bpfmaniov1alpha1.BpfNsProgramList{} + bpfDeletedPrograms := &bpfmaniov1alpha1.BpfNsProgramList{} + // find programs that need to be deleted and delete them + opts := []client.ListOption{client.MatchingLabels{internal.BpfProgramOwner: a.Name}} + if err := r.List(ctx, bpfPrograms, opts...); err != nil { + r.Logger.Error(err, "failed to get freshPrograms for full reconcile") + return ctrl.Result{}, err + } + for _, bpfProgram := range bpfPrograms.Items { + id := bpfProgram.Labels[internal.AppProgramId] + if _, ok := appProgramMap[id]; !ok { + r.Logger.Info("Deleting BpfNsProgram", "AppProgramId", id, "BpfNsProgram", bpfProgram.Name) + bpfDeletedPrograms.Items = append(bpfDeletedPrograms.Items, bpfProgram) + } + } + // Delete BpfNsPrograms that are no longer needed + res, err = r.unLoadAndDeleteBpfProgramsList(ctx, lastRec, bpfDeletedPrograms, internal.BpfNsApplicationControllerFinalizer) + if err != nil { + r.Logger.Error(err, "failed to delete programs") + return ctrl.Result{}, err + } + // We've completed reconciling all programs for this application, continue to the next one + continue + } else { + return res, err + } + } + + return res, err +} + +// SetupWithManager sets up the controller with the Manager. +// The Bpfman-Agent should reconcile whenever a BpfNsApplication object is updated, +// load the programs to the node via bpfman, and then create a BpfNsProgram object +// to reflect per node state information. +func (r *BpfNsApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.BpfNsApplication{}, builder.WithPredicates(predicate.And(predicate.GenerationChangedPredicate{}, predicate.ResourceVersionChangedPredicate{}))). + Owns(&bpfmaniov1alpha1.BpfNsProgram{}, + builder.WithPredicates(predicate.And( + internal.BpfNsProgramTypePredicate(internal.ApplicationString), + internal.BpfProgramNodePredicate(r.NodeName)), + ), + ). + // Only trigger reconciliation if node labels change since that could + // make the BpfNsApplication no longer select the Node. Additionally only + // care about node events specific to our node + Watches( + &v1.Node{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And(predicate.LabelChangedPredicate{}, nodePredicate(r.NodeName))), + ). + // Watch for changes in Pod resources in case we are using a container selector. + Watches( + &v1.Pod{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(podOnNodePredicate(r.NodeName)), + ). + Complete(r) +} diff --git a/controllers/bpfman-agent/application-ns-program_test.go b/controllers/bpfman-agent/application-ns-program_test.go new file mode 100644 index 000000000..ec441f90d --- /dev/null +++ b/controllers/bpfman-agent/application-ns-program_test.go @@ -0,0 +1,364 @@ +package bpfmanagent + +import ( + "context" + "fmt" + "testing" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" + "github.com/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" +) + +func TestBpfNsApplicationControllerCreate(t *testing.T) { + var ( + // global config + name = "fakeAppProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + fakeNode = testutils.NewNode("fake-control-plane") + fakePodName = "my-pod" + fakeContainerName = "my-container-1" + fakePid = int64(4490) + ctx = context.TODO() + bpfUprobeFunctionName = "test" + uprobeFunctionName = "malloc" + uprobeTarget = "libc" + uprobeAppProgramId = fmt.Sprintf("%s-%s-%s", "uprobe", sanitize(uprobeFunctionName), bpfUprobeFunctionName) + uprobeBpfProg = &bpfmaniov1alpha1.BpfNsProgram{} + uprobeFakeUID = "ef71d42c-aa21-48e8-a697-82391d801a80" + uprobeOffset = 0 + uprobeRetprobe = false + uprobeAttachPoint = fmt.Sprintf("%s-%s-%s-%s", + sanitize(uprobeTarget), + sanitize(uprobeFunctionName), + fakePodName, + fakeContainerName, + ) + // xdp program config + bpfXdpFunctionName = "test" + xdpFakeInt = "eth0" + xdpAppProgramId = fmt.Sprintf("%s-%s", "xdp", bpfXdpFunctionName) + xdpBpfProg = &bpfmaniov1alpha1.BpfNsProgram{} + xdpFakeUID = "ef71d42c-aa21-48e8-a697-82391d801a82" + xdpAttachPoint = fmt.Sprintf("%s-%s-%s", + xdpFakeInt, + fakePodName, + fakeContainerName, + ) + ) + + var fakeInts = []string{xdpFakeInt} + + // A AppProgram object with metadata and spec. + App := &bpfmaniov1alpha1.BpfNsApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: bpfmaniov1alpha1.BpfNsApplicationSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + Programs: []bpfmaniov1alpha1.BpfNsApplicationProgram{ + { + Type: bpfmaniov1alpha1.ProgTypeUprobe, + Uprobe: &bpfmaniov1alpha1.UprobeNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfUprobeFunctionName, + }, + FunctionName: uprobeFunctionName, + Target: uprobeTarget, + Offset: uint64(uprobeOffset), + RetProbe: uprobeRetprobe, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + }, + }, + { + Type: bpfmaniov1alpha1.ProgTypeXDP, + XDP: &bpfmaniov1alpha1.XdpNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfXdpFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &fakeInts, + }, + Priority: 0, + ProceedOn: []bpfmaniov1alpha1.XdpProceedOnValue{bpfmaniov1alpha1.XdpProceedOnValue("pass"), + bpfmaniov1alpha1.XdpProceedOnValue("dispatcher_return"), + }, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + }, + }, + }, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, App} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, App) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsApplicationList{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsApplication{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder(). + WithStatusSubresource(App). + WithStatusSubresource(&bpfmaniov1alpha1.BpfNsProgram{}). + WithRuntimeObjects(objs...).Build() + + cli := agenttestutils.NewBpfmanClientFake() + + testContainers := FakeContainerGetter{ + containerList: &[]ContainerInfo{ + { + podName: fakePodName, + containerName: fakeContainerName, + pid: fakePid, + }, + }, + } + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + BpfmanClient: cli, + NodeName: fakeNode.Name, + Containers: &testContainers, + appOwner: App, + } + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger, so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &BpfNsApplicationReconciler{NamespaceProgramReconciler: npr, ourNode: fakeNode} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should create the bpf program object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Check the BpfNsProgram Object was created successfully + ruprobe := &UprobeNsProgramReconciler{NamespaceProgramReconciler: npr, ourNode: fakeNode} + err = r.getBpfProgram(ctx, ruprobe, name, uprobeAppProgramId, uprobeAttachPoint, uprobeBpfProg) + require.NoError(t, err) + + require.NotEmpty(t, uprobeBpfProg) + // Finalizer is written + require.Equal(t, internal.BpfNsApplicationControllerFinalizer, uprobeBpfProg.Finalizers[0]) + // owningConfig Label was correctly set + require.Equal(t, name, uprobeBpfProg.Labels[internal.BpfProgramOwner]) + // node Label was correctly set + require.Equal(t, fakeNode.Name, uprobeBpfProg.Labels[internal.K8sHostLabel]) + // uprobe target Annotation was correctly set + require.Equal(t, uprobeTarget, uprobeBpfProg.Annotations[internal.UprobeNsProgramTarget]) + // Type is set + require.Equal(t, r.getRecType(), uprobeBpfProg.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of BpfNsProgram with Fake UID since the fake API server won't + uprobeBpfProg.UID = types.UID(uprobeFakeUID) + err = cl.Update(ctx, uprobeBpfProg) + require.NoError(t, err) + + // Second reconcile should create the bpfman Load Request and update the + // BpfNsProgram object's maps field and id annotation. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + pid32 := int32(fakePid) + + // Require no requeue + require.False(t, res.Requeue) + + // do Uprobe Program + expectedLoadReq := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfUprobeFunctionName, + ProgramType: *internal.Kprobe.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(uprobeBpfProg.UID), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_UprobeAttachInfo{ + UprobeAttachInfo: &gobpfman.UprobeAttachInfo{ + FnName: &uprobeFunctionName, + Target: uprobeTarget, + Offset: uint64(uprobeOffset), + Retprobe: uprobeRetprobe, + ContainerPid: &pid32, + }, + }, + }, + } + + // Check that the BpfNsProgram's programs was correctly updated + err = r.getBpfProgram(ctx, ruprobe, name, uprobeAppProgramId, uprobeAttachPoint, uprobeBpfProg) + require.NoError(t, err) + + // prog ID should already have been set + id, err := GetID(uprobeBpfProg) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly Built + if !cmp.Equal(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) { + cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) + t.Logf("Diff %v", cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Third reconcile should set the status to loaded + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check that the BpfNsProgram's status was correctly updated + err = r.getBpfProgram(ctx, ruprobe, name, uprobeAppProgramId, uprobeAttachPoint, uprobeBpfProg) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), uprobeBpfProg.Status.Conditions[0].Type) + + // do xdp program + // First reconcile should create the bpf program object + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + rxdp := &XdpNsProgramReconciler{NamespaceProgramReconciler: npr, ourNode: fakeNode} + err = r.getBpfProgram(ctx, rxdp, name, xdpAppProgramId, xdpAttachPoint, xdpBpfProg) + require.NoError(t, err) + + require.NotEmpty(t, xdpBpfProg) + // Finalizer is written + require.Equal(t, internal.BpfNsApplicationControllerFinalizer, xdpBpfProg.Finalizers[0]) + // owningConfig Label was correctly set + require.Equal(t, name, xdpBpfProg.Labels[internal.BpfProgramOwner]) + // node Label was correctly set + require.Equal(t, fakeNode.Name, xdpBpfProg.Labels[internal.K8sHostLabel]) + // Type is set + require.Equal(t, r.getRecType(), xdpBpfProg.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of BpfNsProgram with Fake UID since the fake API server won't + xdpBpfProg.UID = types.UID(xdpFakeUID) + err = cl.Update(ctx, xdpBpfProg) + require.NoError(t, err) + + // Second reconcile should create the bpfman Load Request and update the + // BpfNsProgram object's maps field and id annotation. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + netns := fmt.Sprintf("/host/proc/%d/ns/net", fakePid) + + expectedLoadReq = &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfXdpFunctionName, + ProgramType: *internal.Xdp.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(xdpBpfProg.UID), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_XdpAttachInfo{ + XdpAttachInfo: &gobpfman.XDPAttachInfo{ + Priority: 0, + Iface: fakeInts[0], + ProceedOn: []int32{2, 31}, + Netns: &netns, + }, + }, + }, + } + + // Check that the BpfNsProgram's programs was correctly updated + err = r.getBpfProgram(ctx, rxdp, name, xdpAppProgramId, xdpAttachPoint, xdpBpfProg) + require.NoError(t, err) + + // prog ID should already have been set + id, err = GetID(xdpBpfProg) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly Built + if !cmp.Equal(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) { + cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) + t.Logf("Diff %v", cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Third reconcile should set the status to loaded + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check that the BpfNsProgram's status was correctly updated + err = r.getBpfProgram(ctx, rxdp, name, xdpAppProgramId, xdpAttachPoint, xdpBpfProg) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), xdpBpfProg.Status.Conditions[0].Type) +} diff --git a/controllers/bpfman-agent/application-program.go b/controllers/bpfman-agent/application-program.go index ac5b019ef..151e7680f 100644 --- a/controllers/bpfman-agent/application-program.go +++ b/controllers/bpfman-agent/application-program.go @@ -1,3 +1,19 @@ +/* +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 bpfmanagent import ( @@ -21,7 +37,7 @@ import ( //+kubebuilder:rbac:groups=bpfman.io,resources=bpfapplications,verbs=get;list;watch type BpfApplicationReconciler struct { - ReconcilerCommon + ClusterProgramReconciler currentApp *bpfmaniov1alpha1.BpfApplication ourNode *v1.Node } @@ -64,6 +80,7 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque var res ctrl.Result var err error var complete bool + var lastRec bpfmanReconciler[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] buildProgramName := func( app bpfmaniov1alpha1.BpfApplication, @@ -87,15 +104,16 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque }, } rec := &FentryProgramReconciler{ - ReconcilerCommon: r.ReconcilerCommon, - currentFentryProgram: &fentryProgram, - ourNode: r.ourNode, + ClusterProgramReconciler: r.ClusterProgramReconciler, + currentFentryProgram: &fentryProgram, + ourNode: r.ourNode, } rec.appOwner = &a fentryObjects := []client.Object{&fentryProgram} appProgramMap[appProgramId] = true // Reconcile FentryProgram. complete, res, err = r.reconcileCommon(ctx, rec, fentryObjects) + lastRec = rec case bpfmaniov1alpha1.ProgTypeFexit: appProgramId := fmt.Sprintf("%s-%s-%s", strings.ToLower(string(p.Type)), sanitize(p.Fexit.FunctionName), p.Fexit.BpfFunctionName) @@ -109,15 +127,16 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque }, } rec := &FexitProgramReconciler{ - ReconcilerCommon: r.ReconcilerCommon, - currentFexitProgram: &fexitProgram, - ourNode: r.ourNode, + ClusterProgramReconciler: r.ClusterProgramReconciler, + currentFexitProgram: &fexitProgram, + ourNode: r.ourNode, } rec.appOwner = &a fexitObjects := []client.Object{&fexitProgram} appProgramMap[appProgramId] = true // Reconcile FexitProgram. complete, res, err = r.reconcileCommon(ctx, rec, fexitObjects) + lastRec = rec case bpfmaniov1alpha1.ProgTypeKprobe, bpfmaniov1alpha1.ProgTypeKretprobe: @@ -132,15 +151,16 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque }, } rec := &KprobeProgramReconciler{ - ReconcilerCommon: r.ReconcilerCommon, - currentKprobeProgram: &kprobeProgram, - ourNode: r.ourNode, + ClusterProgramReconciler: r.ClusterProgramReconciler, + currentKprobeProgram: &kprobeProgram, + ourNode: r.ourNode, } rec.appOwner = &a kprobeObjects := []client.Object{&kprobeProgram} appProgramMap[appProgramId] = true // Reconcile KprobeProgram or KpretprobeProgram. complete, res, err = r.reconcileCommon(ctx, rec, kprobeObjects) + lastRec = rec case bpfmaniov1alpha1.ProgTypeUprobe, bpfmaniov1alpha1.ProgTypeUretprobe: @@ -155,15 +175,16 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque }, } rec := &UprobeProgramReconciler{ - ReconcilerCommon: r.ReconcilerCommon, - currentUprobeProgram: &uprobeProgram, - ourNode: r.ourNode, + ClusterProgramReconciler: r.ClusterProgramReconciler, + currentUprobeProgram: &uprobeProgram, + ourNode: r.ourNode, } rec.appOwner = &a uprobeObjects := []client.Object{&uprobeProgram} appProgramMap[appProgramId] = true // Reconcile UprobeProgram or UpretprobeProgram. complete, res, err = r.reconcileCommon(ctx, rec, uprobeObjects) + lastRec = rec case bpfmaniov1alpha1.ProgTypeTracepoint: appProgramId := fmt.Sprintf("%s-%s", strings.ToLower(string(p.Type)), p.Tracepoint.BpfFunctionName) @@ -177,7 +198,7 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque }, } rec := &TracepointProgramReconciler{ - ReconcilerCommon: r.ReconcilerCommon, + ClusterProgramReconciler: r.ClusterProgramReconciler, currentTracepointProgram: &tracepointProgram, ourNode: r.ourNode, } @@ -186,6 +207,7 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque appProgramMap[appProgramId] = true // Reconcile TracepointProgram. complete, res, err = r.reconcileCommon(ctx, rec, tracepointObjects) + lastRec = rec case bpfmaniov1alpha1.ProgTypeTC: _, ifErr := getInterfaces(&p.TC.InterfaceSelector, r.ourNode) @@ -205,15 +227,16 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque }, } rec := &TcProgramReconciler{ - ReconcilerCommon: r.ReconcilerCommon, - currentTcProgram: &tcProgram, - ourNode: r.ourNode, + ClusterProgramReconciler: r.ClusterProgramReconciler, + currentTcProgram: &tcProgram, + ourNode: r.ourNode, } rec.appOwner = &a tcObjects := []client.Object{&tcProgram} appProgramMap[appProgramId] = true // Reconcile TcProgram. complete, res, err = r.reconcileCommon(ctx, rec, tcObjects) + lastRec = rec case bpfmaniov1alpha1.ProgTypeTCX: _, ifErr := getInterfaces(&p.TCX.InterfaceSelector, r.ourNode) @@ -233,15 +256,16 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque }, } rec := &TcxProgramReconciler{ - ReconcilerCommon: r.ReconcilerCommon, - currentTcxProgram: &tcxProgram, - ourNode: r.ourNode, + ClusterProgramReconciler: r.ClusterProgramReconciler, + currentTcxProgram: &tcxProgram, + ourNode: r.ourNode, } rec.appOwner = &a tcxObjects := []client.Object{&tcxProgram} appProgramMap[appProgramId] = true // Reconcile TcxProgram. complete, res, err = r.reconcileCommon(ctx, rec, tcxObjects) + lastRec = rec case bpfmaniov1alpha1.ProgTypeXDP: _, ifErr := getInterfaces(&p.XDP.InterfaceSelector, r.ourNode) @@ -261,15 +285,16 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque }, } rec := &XdpProgramReconciler{ - ReconcilerCommon: r.ReconcilerCommon, - currentXdpProgram: &xdpProgram, - ourNode: r.ourNode, + ClusterProgramReconciler: r.ClusterProgramReconciler, + currentXdpProgram: &xdpProgram, + ourNode: r.ourNode, } rec.appOwner = &a xdpObjects := []client.Object{&xdpProgram} appProgramMap[appProgramId] = true // Reconcile XdpProgram. complete, res, err = r.reconcileCommon(ctx, rec, xdpObjects) + lastRec = rec default: r.Logger.Error(fmt.Errorf("unsupported bpf program type"), "unsupported bpf program type", "ProgType", p.Type) @@ -305,7 +330,7 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque } } // Delete BpfPrograms that are no longer needed - res, err = r.unLoadAndDeleteBpfProgramsList(ctx, bpfDeletedPrograms, internal.BpfApplicationControllerFinalizer) + res, err = r.unLoadAndDeleteBpfProgramsList(ctx, lastRec, bpfDeletedPrograms, internal.BpfApplicationControllerFinalizer) if err != nil { r.Logger.Error(err, "failed to delete programs") return ctrl.Result{}, err diff --git a/controllers/bpfman-agent/application-program_test.go b/controllers/bpfman-agent/application-program_test.go index d3c0195c7..067f4c581 100644 --- a/controllers/bpfman-agent/application-program_test.go +++ b/controllers/bpfman-agent/application-program_test.go @@ -6,7 +6,6 @@ import ( "testing" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" "github.com/bpfman/bpfman-operator/internal" testutils "github.com/bpfman/bpfman-operator/internal/test-utils" @@ -105,19 +104,22 @@ func TestBpfApplicationControllerCreate(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, appOwner: App, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger, so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &BpfApplicationReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &BpfApplicationReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -135,7 +137,8 @@ func TestBpfApplicationControllerCreate(t *testing.T) { } // Check the BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, fentryAppProgramId, fentryAttachPoint, fentryBpfProg) + rfentry := &FentryProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} + err = r.getBpfProgram(ctx, rfentry, name, fentryAppProgramId, fentryAttachPoint, fentryBpfProg) require.NoError(t, err) require.NotEmpty(t, fentryBpfProg) @@ -186,11 +189,11 @@ func TestBpfApplicationControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = rc.getBpfProgram(ctx, name, fentryAppProgramId, fentryAttachPoint, fentryBpfProg) + err = r.getBpfProgram(ctx, rfentry, name, fentryAppProgramId, fentryAttachPoint, fentryBpfProg) require.NoError(t, err) // prog ID should already have been set - id, err := bpfmanagentinternal.GetID(fentryBpfProg) + id, err := GetID(fentryBpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly Built @@ -210,7 +213,7 @@ func TestBpfApplicationControllerCreate(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, fentryAppProgramId, fentryAttachPoint, fentryBpfProg) + err = r.getBpfProgram(ctx, rfentry, name, fentryAppProgramId, fentryAttachPoint, fentryBpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), fentryBpfProg.Status.Conditions[0].Type) @@ -222,7 +225,8 @@ func TestBpfApplicationControllerCreate(t *testing.T) { t.Fatalf("reconcile: (%v)", err) } - err = rc.getBpfProgram(ctx, name, kprobeAppProgramId, kprobeAttachPoint, kprobeBpfProg) + rkprobe := &KprobeProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} + err = r.getBpfProgram(ctx, rkprobe, name, kprobeAppProgramId, kprobeAttachPoint, kprobeBpfProg) require.NoError(t, err) require.NotEmpty(t, kprobeBpfProg) @@ -275,11 +279,11 @@ func TestBpfApplicationControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = rc.getBpfProgram(ctx, name, kprobeAppProgramId, kprobeAttachPoint, kprobeBpfProg) + err = r.getBpfProgram(ctx, rkprobe, name, kprobeAppProgramId, kprobeAttachPoint, kprobeBpfProg) require.NoError(t, err) // prog ID should already have been set - id, err = bpfmanagentinternal.GetID(kprobeBpfProg) + id, err = GetID(kprobeBpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly Built @@ -299,7 +303,7 @@ func TestBpfApplicationControllerCreate(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, kprobeAppProgramId, kprobeAttachPoint, kprobeBpfProg) + err = r.getBpfProgram(ctx, rkprobe, name, kprobeAppProgramId, kprobeAttachPoint, kprobeBpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), kprobeBpfProg.Status.Conditions[0].Type) diff --git a/controllers/bpfman-agent/common.go b/controllers/bpfman-agent/common.go index a18e796c8..21ca76d2c 100644 --- a/controllers/bpfman-agent/common.go +++ b/controllers/bpfman-agent/common.go @@ -25,10 +25,10 @@ import ( "time" v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" @@ -59,17 +59,40 @@ import ( //+kubebuilder:rbac:groups=bpfman.io,resources=fentryprograms/finalizers,verbs=update //+kubebuilder:rbac:groups=bpfman.io,resources=fexityprograms/finalizers,verbs=update //+kubebuilder:rbac:groups=bpfman.io,resources=bpfapplications/finalizers,verbs=update -//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch -//+kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch -//+kubebuilder:rbac:groups=core,resources=secrets,verbs=get +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfnsprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfnsprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfnsprograms/finalizers,verbs=update +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=xdpnsprograms/finalizers,verbs=update +// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch +// +kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch +// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get const ( retryDurationAgent = 5 * time.Second programDoesNotExistErr = "does not exist" ) +type BpfProg interface { + + // GetName returns the name of the current program. + GetName() string + + // GetUID returns the UID of the current program. + GetUID() types.UID + GetAnnotations() map[string]string + GetLabels() map[string]string + GetStatus() *bpfmaniov1alpha1.BpfProgramStatus + GetClientObject() client.Object +} + +type BpfProgList[T any] interface { + // bpfmaniov1alpha1.BpfProgramList | bpfmaniov1alpha1.BpfNsProgramList + + GetItems() []T +} + // ReconcilerCommon provides a skeleton for all *Program Reconcilers. -type ReconcilerCommon struct { +type ReconcilerCommon[T BpfProg, TL BpfProgList[T]] struct { client.Client Scheme *runtime.Scheme GrpcConn *grpc.ClientConn @@ -85,7 +108,24 @@ type ReconcilerCommon struct { // bpfmanReconciler defines a generic bpfProgram K8s object reconciler which can // program bpfman from user intent in the K8s CRDs. -type bpfmanReconciler interface { +type bpfmanReconciler[T BpfProg, TL BpfProgList[T]] interface { + // BPF Cluster of Namespaced Reconciler + // + // createBpfProgram creates either a BpfProgram or BpfNsProgram instance. It is pushed + // to the Kubernetes API server at a later time. + createBpfProgram( + attachPoint string, + rec bpfmanReconciler[T, TL], + annotations map[string]string, + ) (*T, error) + // getBpfList calls the Kubernetes API server to retrieve a list of BpfProgram or BpfNsProgram objects. + getBpfList(ctx context.Context, opts []client.ListOption) (*TL, error) + // updateBpfStatus calls the Kubernetes API server to write a new condition to the Status of a + // BpfProgram or BpfNsProgram object. + updateBpfStatus(ctx context.Context, bpfProgram *T, condition metav1.Condition) error + + // *Program Reconciler + // // SetupWithManager registers the reconciler with the manager and defines // which kubernetes events will trigger a reconcile. SetupWithManager(mgr ctrl.Manager) error @@ -111,12 +151,18 @@ type bpfmanReconciler interface { getProgType() internal.ProgramType // getName returns the name of the current program being reconciled. getName() string + // getNamespace returns the namespace of the current program being reconciled. + getNamespace() string + // getNoContAnnotationIndex returns the index into the annotations map for the "NoContainersOnNode" + // field. Each program type has its own unique index, and common code needs to look in the annotations + // to determine if No Containers were found. Example indexes: TcNoContainersOnNode, XdpNoContainersOnNode, ... + getNoContAnnotationIndex() string // getExpectedBpfPrograms returns the list of BpfPrograms that are expected // to be loaded on the current node. - getExpectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfProgramList, error) + getExpectedBpfPrograms(ctx context.Context) (*TL, error) // getLoadRequest returns the LoadRequest that should be sent to bpfman to // load the given BpfProgram. - getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfProgram, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) + getLoadRequest(bpfProgram *T, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) // getNode returns node object for the current node. getNode() *v1.Node // getBpfProgramCommon returns the BpfProgramCommon object for the current @@ -143,7 +189,7 @@ type bpfmanReconciler interface { // user and retry if specified. For some errors the controller may decide not to // retry. Note: This only results in calls to bpfman if we need to change // something -func (r *ReconcilerCommon) reconcileCommon(ctx context.Context, rec bpfmanReconciler, +func (r *ReconcilerCommon[T, TL]) reconcileCommon(ctx context.Context, rec bpfmanReconciler[T, TL], programs []client.Object) (bool, ctrl.Result, error) { r.Logger.V(1).Info("Start reconcileCommon()") @@ -195,18 +241,18 @@ func (r *ReconcilerCommon) reconcileCommon(ctx context.Context, rec bpfmanReconc // reconcileBpfmanPrograms ONLY reconciles the bpfman state for a single BpfProgram. // It does not interact with the k8s API in any way. -func (r *ReconcilerCommon) reconcileBpfProgram(ctx context.Context, - rec bpfmanReconciler, +func (r *ReconcilerCommon[T, TL]) reconcileBpfProgram(ctx context.Context, + rec bpfmanReconciler[T, TL], loadedBpfPrograms map[string]*gobpfman.ListResponse_ListResult, - bpfProgram *bpfmaniov1alpha1.BpfProgram, + bpfProgram *T, isNodeSelected bool, isBeingDeleted bool, mapOwnerStatus *MapOwnerParamStatus) (bpfmaniov1alpha1.BpfProgramConditionType, error) { - r.Logger.V(1).Info("enter reconcileBpfmanProgram()", "Name", bpfProgram.Name, "CurrentProgram", rec.getName()) + r.Logger.V(1).Info("enter reconcileBpfmanProgram()", "Name", (*bpfProgram).GetName(), "CurrentProgram", rec.getName()) - uuid := bpfProgram.UID - noContainersOnNode := noContainersOnNode(bpfProgram) + uuid := (*bpfProgram).GetUID() + noContainersOnNode := noContainersOnNode(bpfProgram, rec.getNoContAnnotationIndex()) loadedBpfProgram, isLoaded := loadedBpfPrograms[string(uuid)] shouldBeLoaded := bpfProgramShouldBeLoaded(isNodeSelected, isBeingDeleted, noContainersOnNode, mapOwnerStatus) @@ -215,7 +261,7 @@ func (r *ReconcilerCommon) reconcileBpfProgram(ctx context.Context, switch isLoaded { case true: // prog ID should already have been set if program is loaded - id, err := bpfmanagentinternal.GetID(bpfProgram) + id, err := GetID(bpfProgram) if err != nil { r.Logger.Error(err, "Failed to get kernel ID for BpfProgram") return bpfmaniov1alpha1.BpfProgCondNotLoaded, err @@ -230,13 +276,13 @@ func (r *ReconcilerCommon) reconcileBpfProgram(ctx context.Context, } isSame, reasons := bpfmanagentinternal.DoesProgExist(loadedBpfProgram, loadRequest) if !isSame { - r.Logger.Info("BpfProgram is in wrong state, unloading and reloading", "reason", reasons, "Name", bpfProgram.Name, "Program ID", id) + r.Logger.Info("BpfProgram is in wrong state, unloading and reloading", "reason", reasons, "Name", (*bpfProgram).GetName(), "Program ID", id) if err := bpfmanagentinternal.UnloadBpfmanProgram(ctx, r.BpfmanClient, *id); err != nil { r.Logger.Error(err, "Failed to unload eBPF Program") return bpfmaniov1alpha1.BpfProgCondNotUnloaded, err } - r.Logger.Info("Calling bpfman to load eBPF Program on Node", "Name", bpfProgram.Name) + r.Logger.Info("Calling bpfman to load eBPF Program on Node", "Name", (*bpfProgram).GetName()) r.progId, err = bpfmanagentinternal.LoadBpfmanProgram(ctx, r.BpfmanClient, loadRequest) if err != nil { r.Logger.Error(err, "Failed to load eBPF Program") @@ -249,7 +295,7 @@ func (r *ReconcilerCommon) reconcileBpfProgram(ctx context.Context, } case false: // The program is loaded but it shouldn't be loaded. - r.Logger.Info("Calling bpfman to unload eBPF Program on node", "Name", bpfProgram.Name, "Program ID", id) + r.Logger.Info("Calling bpfman to unload eBPF Program on node", "Name", (*bpfProgram).GetName(), "Program ID", id) if err := bpfmanagentinternal.UnloadBpfmanProgram(ctx, r.BpfmanClient, *id); err != nil { r.Logger.Error(err, "Failed to unload eBPF Program") return bpfmaniov1alpha1.BpfProgCondNotUnloaded, err @@ -264,7 +310,7 @@ func (r *ReconcilerCommon) reconcileBpfProgram(ctx context.Context, return bpfmaniov1alpha1.BpfProgCondBytecodeSelectorError, err } - r.Logger.Info("Calling bpfman to load eBPF Program on node", "Name", bpfProgram.Name) + r.Logger.Info("Calling bpfman to load eBPF Program on node", "Name", (*bpfProgram).GetName()) r.progId, err = bpfmanagentinternal.LoadBpfmanProgram(ctx, r.BpfmanClient, loadRequest) if err != nil { r.Logger.Error(err, "Failed to load eBPF Program") @@ -287,7 +333,7 @@ func (r *ReconcilerCommon) reconcileBpfProgram(ctx context.Context, // reconcileBpfProgramSuccessCondition returns the proper condition for a // successful reconcile of a bpfProgram based on the given parameters. -func (r *ReconcilerCommon) reconcileBpfProgramSuccessCondition( +func (r *ReconcilerCommon[T, TL]) reconcileBpfProgramSuccessCondition( isLoaded bool, shouldBeLoaded bool, isNodeSelected bool, @@ -452,7 +498,7 @@ func getInterfaces(interfaceSelector *bpfmaniov1alpha1.InterfaceSelector, ourNod // removeFinalizer removes the finalizer from the BpfProgram object if is applied, // returning if the action resulted in a kube API update or not along with any // errors. -func (r *ReconcilerCommon) removeFinalizer(ctx context.Context, o client.Object, finalizer string) bool { +func (r *ReconcilerCommon[T, TL]) removeFinalizer(ctx context.Context, o client.Object, finalizer string) bool { changed := controllerutil.RemoveFinalizer(o, finalizer) if changed { r.Logger.Info("Calling KubeAPI to remove finalizer from BpfProgram", "object name", o.GetName()) @@ -469,36 +515,42 @@ func (r *ReconcilerCommon) removeFinalizer(ctx context.Context, o client.Object, // updateStatus updates the status of a BpfProgram object if needed, returning // false if the status was already set for the given bpfProgram, meaning reconciliation // may continue. -func (r *ReconcilerCommon) updateStatus(ctx context.Context, bpfProgram *bpfmaniov1alpha1.BpfProgram, cond bpfmaniov1alpha1.BpfProgramConditionType) bool { - - r.Logger.V(1).Info("updateStatus()", "existing conds", bpfProgram.Status.Conditions, "new cond", cond) +func (r *ReconcilerCommon[T, TL]) updateStatus( + ctx context.Context, + rec bpfmanReconciler[T, TL], + bpfProgram *T, + cond bpfmaniov1alpha1.BpfProgramConditionType, +) bool { + status := (*bpfProgram).GetStatus() + r.Logger.V(1).Info("updateStatus()", "existing conds", status.Conditions, "new cond", cond) - if bpfProgram.Status.Conditions != nil { - numConditions := len(bpfProgram.Status.Conditions) + if status.Conditions != nil { + numConditions := len(status.Conditions) if numConditions == 1 { - if bpfProgram.Status.Conditions[0].Type == string(cond) { + if status.Conditions[0].Type == string(cond) { // No change, so just return false -- not updated return false } else { // We're changing the condition, so delete this one. The // new condition will be added below. - bpfProgram.Status.Conditions = nil + status.Conditions = nil } } else if numConditions > 1 { // We should only ever have one condition, so we shouldn't hit this // case. However, if we do, log a message, delete the existing // conditions, and add the new one below. r.Logger.Info("more than one BpfProgramCondition", "numConditions", numConditions) - bpfProgram.Status.Conditions = nil + status.Conditions = nil } // if numConditions == 0, just add the new condition below. } - meta.SetStatusCondition(&bpfProgram.Status.Conditions, cond.Condition()) + //meta.SetStatusCondition(&status.Conditions, cond.Condition()) - r.Logger.Info("Calling KubeAPI to update BpfProgram condition", "Name", bpfProgram.Name, "condition", cond.Condition().Type) - if err := r.Status().Update(ctx, bpfProgram); err != nil { + r.Logger.Info("Calling KubeAPI to update BpfProgram condition", "Name", (*bpfProgram).GetName(), "condition", cond.Condition().Type) + //if err := r.Status().Update(ctx, (*bpfProgram).GetClientObject()); err != nil { + if err := rec.updateBpfStatus(ctx, bpfProgram, cond.Condition()); err != nil { r.Logger.Error(err, "failed to set BpfProgram object status") } @@ -506,8 +558,10 @@ func (r *ReconcilerCommon) updateStatus(ctx context.Context, bpfProgram *bpfmani return true } -func statusContains(bpfProgram *bpfmaniov1alpha1.BpfProgram, cond bpfmaniov1alpha1.BpfProgramConditionType) bool { - for _, c := range bpfProgram.Status.Conditions { +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func statusContains[T BpfProg](bpfProgram *T, cond bpfmaniov1alpha1.BpfProgramConditionType) bool { + status := (*bpfProgram).GetStatus() + for _, c := range status.Conditions { if c.Type == string(cond) { return true } @@ -520,10 +574,8 @@ type bpfProgKey struct { attachPoint string } -func (r *ReconcilerCommon) getExistingBpfPrograms(ctx context.Context, - rec bpfmanReconciler) (map[bpfProgKey]bpfmaniov1alpha1.BpfProgram, error) { - - bpfProgramList := &bpfmaniov1alpha1.BpfProgramList{} +func (r *ReconcilerCommon[T, TL]) getExistingBpfPrograms(ctx context.Context, + rec bpfmanReconciler[T, TL]) (map[bpfProgKey]T, error) { // Only list bpfPrograms for this *Program and the controller's node opts := []client.ListOption{ @@ -534,13 +586,13 @@ func (r *ReconcilerCommon) getExistingBpfPrograms(ctx context.Context, }, } - err := r.List(ctx, bpfProgramList, opts...) + bpfProgramList, err := rec.getBpfList(ctx, opts) if err != nil { return nil, err } - existingBpfPrograms := map[bpfProgKey]bpfmaniov1alpha1.BpfProgram{} - for _, bpfProg := range bpfProgramList.Items { + existingBpfPrograms := map[bpfProgKey]T{} + for _, bpfProg := range (*bpfProgramList).GetItems() { key := bpfProgKey{ appProgId: bpfProg.GetLabels()[internal.AppProgramId], attachPoint: bpfProg.GetAnnotations()[internal.BpfProgramAttachPoint], @@ -556,46 +608,6 @@ func generateUniqueName(baseName string) string { return fmt.Sprintf("%s-%s", baseName, uuid[:8]) } -// createBpfProgram moves some shared logic for building bpfProgram objects -// into a central location. -func (r *ReconcilerCommon) createBpfProgram( - attachPoint string, - rec bpfmanReconciler, - annotations map[string]string) (*bpfmaniov1alpha1.BpfProgram, error) { - - r.Logger.V(1).Info("createBpfProgram()", "Name", attachPoint, - "Owner", rec.getOwner().GetName(), "OwnerType", rec.getRecType(), "Name", rec.getName()) - - if annotations == nil { - annotations = make(map[string]string) - } - annotations[internal.BpfProgramAttachPoint] = attachPoint - - bpfProg := &bpfmaniov1alpha1.BpfProgram{ - ObjectMeta: metav1.ObjectMeta{ - Name: generateUniqueName(rec.getName()), - Finalizers: []string{rec.getFinalizer()}, - Labels: map[string]string{ - internal.BpfProgramOwner: rec.getOwner().GetName(), - internal.AppProgramId: rec.getAppProgramId(), - internal.K8sHostLabel: r.NodeName, - }, - Annotations: annotations, - }, - Spec: bpfmaniov1alpha1.BpfProgramSpec{ - Type: rec.getRecType(), - }, - Status: bpfmaniov1alpha1.BpfProgramStatus{Conditions: []metav1.Condition{}}, - } - - // Make the corresponding BpfProgramConfig the owner - if err := ctrl.SetControllerReference(rec.getOwner(), bpfProg, r.Scheme); err != nil { - return nil, fmt.Errorf("failed to set BpfProgram object owner reference: %v", err) - } - - return bpfProg, nil -} - // Programs may be deleted for one of two reasons. The first is that the global // *Program object is being deleted. The second is that the something has // changed on the node that is causing the need to remove individual @@ -613,10 +625,10 @@ func (r *ReconcilerCommon) createBpfProgram( // // For the second case, we need to do the first 2 steps, and then explicitly // delete the bpfPrograms that are no longer needed. -func (r *ReconcilerCommon) handleProgDelete( +func (r *ReconcilerCommon[T, TL]) handleProgDelete( ctx context.Context, - rec bpfmanReconciler, - existingBpfPrograms map[bpfProgKey]bpfmaniov1alpha1.BpfProgram, + rec bpfmanReconciler[T, TL], + existingBpfPrograms map[bpfProgKey]T, loadedBpfPrograms map[string]*gobpfman.ListResponse_ListResult, isNodeSelected bool, isBeingDeleted bool, @@ -625,7 +637,7 @@ func (r *ReconcilerCommon) handleProgDelete( r.Logger.V(1).Info("handleProgDelete()", "isBeingDeleted", isBeingDeleted, "isNodeSelected", isNodeSelected, "mapOwnerStatus", mapOwnerStatus) for _, bpfProgram := range existingBpfPrograms { - r.Logger.V(1).Info("Deleting BpfProgram", "Name", bpfProgram.Name) + r.Logger.V(1).Info("Deleting BpfProgram", "Name", bpfProgram.GetName()) // Reconcile the bpfProgram if error write condition and exit with // retry. cond, err := r.reconcileBpfProgram(ctx, @@ -637,11 +649,11 @@ func (r *ReconcilerCommon) handleProgDelete( mapOwnerStatus, ) if err != nil { - r.updateStatus(ctx, &bpfProgram, cond) + r.updateStatus(ctx, rec, &bpfProgram, cond) return internal.Requeue, fmt.Errorf("failed to delete program from bpfman: %v", err) } - if r.removeFinalizer(ctx, &bpfProgram, rec.getFinalizer()) { + if r.removeFinalizer(ctx, bpfProgram.GetClientObject(), rec.getFinalizer()) { return internal.Updated, nil } @@ -649,7 +661,7 @@ func (r *ReconcilerCommon) handleProgDelete( // We're deleting these programs because the *Program is being // deleted, so update the status and the program will be deleted // when the owner is deleted. - if r.updateStatus(ctx, &bpfProgram, cond) { + if r.updateStatus(ctx, rec, &bpfProgram, cond) { return internal.Updated, nil } } else { @@ -657,8 +669,8 @@ func (r *ReconcilerCommon) handleProgDelete( // to changes that caused the containers to not be selected anymore. // So, explicitly delete them. opts := client.DeleteOptions{} - r.Logger.Info("Calling KubeAPI to delete BpfProgram", "Name", bpfProgram.Name, "Owner", bpfProgram.GetName()) - if err := r.Delete(ctx, &bpfProgram, &opts); err != nil { + r.Logger.Info("Calling KubeAPI to delete BpfProgram", "Name", bpfProgram.GetName(), "Owner", bpfProgram.GetName()) + if err := r.Delete(ctx, bpfProgram.GetClientObject(), &opts); err != nil { return internal.Requeue, fmt.Errorf("failed to delete BpfProgram object: %v", err) } return internal.Updated, nil @@ -673,41 +685,46 @@ func (r *ReconcilerCommon) handleProgDelete( // unLoadAndDeleteProgramsList unloads and deletes BbpPrograms when the owning // *Program or BpfApplication is not being deleted itself, but something // has changed such that the BpfPrograms are no longer needed. -func (r *ReconcilerCommon) unLoadAndDeleteBpfProgramsList(ctx context.Context, bpfProgramsList *bpfmaniov1alpha1.BpfProgramList, finalizerString string) (reconcile.Result, error) { - for _, bpfProgram := range bpfProgramsList.Items { - r.Logger.V(1).Info("Deleting BpfProgram", "Name", bpfProgram.Name) - id, err := bpfmanagentinternal.GetID(&bpfProgram) +func (r *ReconcilerCommon[T, TL]) unLoadAndDeleteBpfProgramsList( + ctx context.Context, + rec bpfmanReconciler[T, TL], + bpfProgramsList *TL, + finalizerString string, +) (reconcile.Result, error) { + for _, bpfProgram := range (*bpfProgramsList).GetItems() { + r.Logger.V(1).Info("Deleting BpfProgram", "Name", bpfProgram.GetName()) + id, err := GetID(&bpfProgram) if err != nil { r.Logger.Error(err, "Failed to get kernel ID from BpfProgram") return ctrl.Result{}, nil } if id != nil && !statusContains(&bpfProgram, bpfmaniov1alpha1.BpfProgCondUnloaded) { - r.Logger.Info("Calling bpfman to unload program on node", "Name", bpfProgram.Name, "Program ID", id) + r.Logger.Info("Calling bpfman to unload program on node", "Name", bpfProgram.GetName(), "Program ID", id) if err := bpfmanagentinternal.UnloadBpfmanProgram(ctx, r.BpfmanClient, *id); err != nil { if strings.Contains(err.Error(), programDoesNotExistErr) { - r.Logger.Info("Program not found on node", "Name", bpfProgram.Name, "Program ID", id) + r.Logger.Info("Program not found on node", "Name", bpfProgram.GetName(), "Program ID", id) } else { r.Logger.Error(err, "Failed to unload Program from bpfman") return ctrl.Result{RequeueAfter: retryDurationAgent}, nil } - if r.updateStatus(ctx, &bpfProgram, bpfmaniov1alpha1.BpfProgCondUnloaded) { + if r.updateStatus(ctx, rec, &bpfProgram, bpfmaniov1alpha1.BpfProgCondUnloaded) { return ctrl.Result{}, nil } } } - if r.updateStatus(ctx, &bpfProgram, bpfmaniov1alpha1.BpfProgCondUnloaded) { + if r.updateStatus(ctx, rec, &bpfProgram, bpfmaniov1alpha1.BpfProgCondUnloaded) { return ctrl.Result{}, nil } - if r.removeFinalizer(ctx, &bpfProgram, finalizerString) { + if r.removeFinalizer(ctx, bpfProgram.GetClientObject(), finalizerString) { return ctrl.Result{}, nil } opts := client.DeleteOptions{} - r.Logger.Info("Calling KubeAPI to delete BpfProgram", "Name", bpfProgram.Name, "Owner", bpfProgram.GetName()) - if err := r.Delete(ctx, &bpfProgram, &opts); err != nil { + r.Logger.Info("Calling KubeAPI to delete BpfProgram", "Name", bpfProgram.GetName(), "Owner", bpfProgram.GetName()) + if err := r.Delete(ctx, bpfProgram.GetClientObject(), &opts); err != nil { return ctrl.Result{RequeueAfter: retryDurationAgent}, fmt.Errorf("failed to delete BpfProgram object: %v", err) } else { // we will deal one program at a time, so we can break out of the loop @@ -721,11 +738,11 @@ func (r *ReconcilerCommon) unLoadAndDeleteBpfProgramsList(ctx context.Context, b // bpfPrograms. If a bpfProgram is expected but doesn't exist, it is created. // If an expected bpfProgram exists, it is reconciled. If a bpfProgram exists // but is not expected, it is deleted. -func (r *ReconcilerCommon) handleProgCreateOrUpdate( +func (r *ReconcilerCommon[T, TL]) handleProgCreateOrUpdate( ctx context.Context, - rec bpfmanReconciler, - existingBpfPrograms map[bpfProgKey]bpfmaniov1alpha1.BpfProgram, - expectedBpfPrograms *bpfmaniov1alpha1.BpfProgramList, + rec bpfmanReconciler[T, TL], + existingBpfPrograms map[bpfProgKey]T, + expectedBpfPrograms *TL, loadedBpfPrograms map[string]*gobpfman.ListResponse_ListResult, isNodeSelected bool, isBeingDeleted bool, @@ -735,8 +752,8 @@ func (r *ReconcilerCommon) handleProgCreateOrUpdate( isNodeSelected, "mapOwnerStatus", mapOwnerStatus) // If the *Program isn't being deleted ALWAYS create the bpfPrograms // even if the node isn't selected - for _, expectedBpfProgram := range expectedBpfPrograms.Items { - r.Logger.V(1).Info("Creating or Updating", "Name", expectedBpfProgram.Name) + for _, expectedBpfProgram := range (*expectedBpfPrograms).GetItems() { + r.Logger.V(1).Info("Creating or Updating", "Name", expectedBpfProgram.GetName()) key := bpfProgKey{ appProgId: expectedBpfProgram.GetLabels()[internal.AppProgramId], attachPoint: expectedBpfProgram.GetAnnotations()[internal.BpfProgramAttachPoint], @@ -749,8 +766,8 @@ func (r *ReconcilerCommon) handleProgCreateOrUpdate( } else { // Create a new bpfProgram Object for this program. opts := client.CreateOptions{} - r.Logger.Info("Calling KubeAPI to create BpfProgram", "Name", expectedBpfProgram.Name, "Owner", rec.getOwner().GetName()) - if err := r.Create(ctx, &expectedBpfProgram, &opts); err != nil { + r.Logger.Info("Calling KubeAPI to create BpfProgram", "Name", expectedBpfProgram.GetName(), "Owner", rec.getOwner().GetName()) + if err := r.Create(ctx, expectedBpfProgram.GetClientObject(), &opts); err != nil { return internal.Requeue, fmt.Errorf("failed to create BpfProgram object: %v", err) } return internal.Updated, nil @@ -767,7 +784,7 @@ func (r *ReconcilerCommon) handleProgCreateOrUpdate( mapOwnerStatus, ) if err != nil { - if r.updateStatus(ctx, &existingBpfProgram, cond) { + if r.updateStatus(ctx, rec, &existingBpfProgram, cond) { // Return an error the first time. return internal.Updated, fmt.Errorf("failed to reconcile bpfman program: %v", err) } @@ -778,7 +795,7 @@ func (r *ReconcilerCommon) handleProgCreateOrUpdate( cond == bpfmaniov1alpha1.BpfProgCondMapOwnerNotLoaded || cond == bpfmaniov1alpha1.BpfProgCondNoContainersOnNode { // Write NodeNodeSelected status - if r.updateStatus(ctx, &existingBpfProgram, cond) { + if r.updateStatus(ctx, rec, &existingBpfProgram, cond) { r.Logger.V(1).Info("Update condition from bpfman reconcile", "condition", cond) return internal.Updated, nil } else { @@ -789,20 +806,22 @@ func (r *ReconcilerCommon) handleProgCreateOrUpdate( // GetID() will fail if ProgramId is not in the annotations, which is expected on a // create. In this case existingId will be nil and DeepEqual() will fail and cause // annotation to be set. - existingId, _ := bpfmanagentinternal.GetID(&existingBpfProgram) + existingId, _ := GetID(&existingBpfProgram) // If bpfProgram Maps OR the program ID annotation isn't up to date just update it and return if !reflect.DeepEqual(existingId, r.progId) { - r.Logger.Info("Calling KubeAPI to update BpfProgram Object", "Id", r.progId, "Name", existingBpfProgram.Name) + r.Logger.Info("Calling KubeAPI to update BpfProgram Object", "Id", r.progId, "Name", existingBpfProgram.GetName()) // annotations should be populated on create - existingBpfProgram.Annotations[internal.IdAnnotation] = strconv.FormatUint(uint64(*r.progId), 10) - if err := r.Update(ctx, &existingBpfProgram, &client.UpdateOptions{}); err != nil { + annotations := existingBpfProgram.GetAnnotations() + annotations[internal.IdAnnotation] = strconv.FormatUint(uint64(*r.progId), 10) + + if err := r.Update(ctx, existingBpfProgram.GetClientObject(), &client.UpdateOptions{}); err != nil { return internal.Requeue, fmt.Errorf("failed to update BpfProgram's Programs: %v", err) } return internal.Updated, nil } - if r.updateStatus(ctx, &existingBpfProgram, cond) { + if r.updateStatus(ctx, rec, &existingBpfProgram, cond) { return internal.Updated, nil } } @@ -827,8 +846,8 @@ func (r *ReconcilerCommon) handleProgCreateOrUpdate( // against the K8s API. If the function returns a retry boolean and error, the // reconcile will be retried based on a default 5 second interval if the retry // boolean is set to `true`. -func (r *ReconcilerCommon) reconcileProgram(ctx context.Context, - rec bpfmanReconciler, +func (r *ReconcilerCommon[T, TL]) reconcileProgram(ctx context.Context, + rec bpfmanReconciler[T, TL], program client.Object, loadedBpfPrograms map[string]*gobpfman.ListResponse_ListResult) (internal.ReconcileResult, error) { @@ -852,7 +871,7 @@ func (r *ReconcilerCommon) reconcileProgram(ctx context.Context, // Determine if the MapOwnerSelector was set, and if so, see if the MapOwner // ID can be found. - mapOwnerStatus, err := r.processMapOwnerParam(ctx, &rec.getBpfProgramCommon().MapOwnerSelector) + mapOwnerStatus, err := r.processMapOwnerParam(ctx, rec, &rec.getBpfProgramCommon().MapOwnerSelector) if err != nil { return internal.Requeue, fmt.Errorf("failed to determine map owner: %v", err) } @@ -897,8 +916,9 @@ type MapOwnerParamStatus struct { // function returns the ID of the BpfProgram that owns the map on this node. // Found or not, this function also returns some flags (isSet, isFound, isLoaded) // to help with the processing and setting of the proper condition on the BpfProgram Object. -func (r *ReconcilerCommon) processMapOwnerParam( +func (r *ReconcilerCommon[T, TL]) processMapOwnerParam( ctx context.Context, + rec bpfmanReconciler[T, TL], selector *metav1.LabelSelector) (*MapOwnerParamStatus, error) { mapOwnerStatus := &MapOwnerParamStatus{} @@ -923,38 +943,38 @@ func (r *ReconcilerCommon) processMapOwnerParam( labelMap[key] = value } opts := []client.ListOption{labelMap} - bpfProgramList := &bpfmaniov1alpha1.BpfProgramList{} r.Logger.V(1).Info("MapOwner Labels:", "opts", opts) - err := r.List(ctx, bpfProgramList, opts...) + bpfProgramList, err := rec.getBpfList(ctx, opts) if err != nil { return mapOwnerStatus, err } // If no BpfProgram Objects were found, or more than one, then return. - if len(bpfProgramList.Items) == 0 { + items := (*bpfProgramList).GetItems() + if len(items) == 0 { return mapOwnerStatus, nil - } else if len(bpfProgramList.Items) > 1 { + } else if len(items) > 1 { return mapOwnerStatus, fmt.Errorf("MapOwnerSelector resolved to multiple BpfProgram Objects") } else { mapOwnerStatus.isFound = true // Get bpfProgram based on UID meta - prog, err := bpfmanagentinternal.GetBpfmanProgram(ctx, r.BpfmanClient, bpfProgramList.Items[0].GetUID()) + prog, err := bpfmanagentinternal.GetBpfmanProgram(ctx, r.BpfmanClient, items[0].GetUID()) if err != nil { - return nil, fmt.Errorf("failed to get bpfman program for BpfProgram with UID %s: %v", bpfProgramList.Items[0].GetUID(), err) + return nil, fmt.Errorf("failed to get bpfman program for BpfProgram with UID %s: %v", items[0].GetUID(), err) } kernelInfo := prog.GetKernelInfo() if kernelInfo == nil { - return nil, fmt.Errorf("failed to process bpfman program for BpfProgram with UID %s: %v", bpfProgramList.Items[0].GetUID(), err) + return nil, fmt.Errorf("failed to process bpfman program for BpfProgram with UID %s: %v", items[0].GetUID(), err) } mapOwnerStatus.mapOwnerId = &kernelInfo.Id // Get most recent condition from the one eBPF Program and determine // if the BpfProgram is loaded or not. - conLen := len(bpfProgramList.Items[0].Status.Conditions) + conLen := len(items[0].GetStatus().Conditions) if conLen > 0 && - bpfProgramList.Items[0].Status.Conditions[conLen-1].Type == + items[0].GetStatus().Conditions[conLen-1].Type == string(bpfmaniov1alpha1.BpfProgCondLoaded) { mapOwnerStatus.isLoaded = true } @@ -999,14 +1019,15 @@ func appProgramId(labels map[string]string) string { // getBpfProgram returns a BpfProgram object in the bpfProgram parameter based // on the given owner, appProgId, and attachPoint. If the BpfProgram is not // found, an error is returned. -func (r *ReconcilerCommon) getBpfProgram( +// +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *ReconcilerCommon[T, TL]) getBpfProgram( ctx context.Context, + rec bpfmanReconciler[T, TL], owner string, appProgId string, attachPoint string, - bpfProgram *bpfmaniov1alpha1.BpfProgram) error { - - bpfProgramList := &bpfmaniov1alpha1.BpfProgramList{} + bpfProgram *T) error { // Only list bpfPrograms for this *Program and the controller's node opts := []client.ListOption{ @@ -1017,12 +1038,12 @@ func (r *ReconcilerCommon) getBpfProgram( }, } - err := r.List(ctx, bpfProgramList, opts...) + bpfProgramList, err := rec.getBpfList(ctx, opts) if err != nil { return err } - for _, bpfProg := range bpfProgramList.Items { + for _, bpfProg := range (*bpfProgramList).GetItems() { if appProgId == bpfProg.GetLabels()[internal.AppProgramId] && attachPoint == bpfProg.GetAnnotations()[internal.BpfProgramAttachPoint] { *bpfProgram = bpfProg @@ -1032,3 +1053,19 @@ func (r *ReconcilerCommon) getBpfProgram( return fmt.Errorf("BpfProgram not found") } + +// get the program ID from a bpfProgram +func GetID[T BpfProg](p *T) (*uint32, error) { + annotations := (*p).GetAnnotations() + idString, ok := annotations[internal.IdAnnotation] + if !ok { + return nil, fmt.Errorf("failed to get program ID because no annotations") + } + + id, err := strconv.ParseUint(idString, 10, 32) + if err != nil { + return nil, fmt.Errorf("failed to parse program ID: %v", err) + } + uid := uint32(id) + return &uid, nil +} diff --git a/controllers/bpfman-agent/common_cluster.go b/controllers/bpfman-agent/common_cluster.go new file mode 100644 index 000000000..8f54514a8 --- /dev/null +++ b/controllers/bpfman-agent/common_cluster.go @@ -0,0 +1,102 @@ +/* +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. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanagent + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman-operator/internal" +) + +type ClusterProgramReconciler struct { + ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] +} + +// createBpfProgram moves some shared logic for building bpfProgram objects +// into a central location. +func (r *ClusterProgramReconciler) createBpfProgram( + attachPoint string, + rec bpfmanReconciler[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList], + annotations map[string]string, +) (*bpfmaniov1alpha1.BpfProgram, error) { + + r.Logger.V(1).Info("createBpfProgram()", "Name", attachPoint, + "Owner", rec.getOwner().GetName(), "OwnerType", rec.getRecType(), "Name", rec.getName()) + + if annotations == nil { + annotations = make(map[string]string) + } + annotations[internal.BpfProgramAttachPoint] = attachPoint + + bpfProg := &bpfmaniov1alpha1.BpfProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: generateUniqueName(rec.getName()), + Finalizers: []string{rec.getFinalizer()}, + Labels: map[string]string{ + internal.BpfProgramOwner: rec.getOwner().GetName(), + internal.AppProgramId: rec.getAppProgramId(), + internal.K8sHostLabel: r.NodeName, + }, + Annotations: annotations, + }, + Spec: bpfmaniov1alpha1.BpfProgramSpec{ + Type: rec.getRecType(), + }, + Status: bpfmaniov1alpha1.BpfProgramStatus{Conditions: []metav1.Condition{}}, + } + + // Make the corresponding BpfProgramConfig the owner + if err := ctrl.SetControllerReference(rec.getOwner(), bpfProg, r.Scheme); err != nil { + return nil, fmt.Errorf("failed to bpfProgram object owner reference: %v", err) + } + + return bpfProg, nil +} + +func (r *ClusterProgramReconciler) getBpfList( + ctx context.Context, + opts []client.ListOption, +) (*bpfmaniov1alpha1.BpfProgramList, error) { + + bpfProgramList := &bpfmaniov1alpha1.BpfProgramList{} + + err := r.List(ctx, bpfProgramList, opts...) + if err != nil { + return nil, err + } + + return bpfProgramList, nil +} + +func (r *ClusterProgramReconciler) updateBpfStatus( + ctx context.Context, + bpfProgram *bpfmaniov1alpha1.BpfProgram, + condition metav1.Condition, +) error { + bpfProgram.Status.Conditions = nil + meta.SetStatusCondition(&bpfProgram.Status.Conditions, condition) + return r.Status().Update(ctx, bpfProgram) +} diff --git a/controllers/bpfman-agent/common_namespace.go b/controllers/bpfman-agent/common_namespace.go new file mode 100644 index 000000000..ad267c3c6 --- /dev/null +++ b/controllers/bpfman-agent/common_namespace.go @@ -0,0 +1,103 @@ +/* +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. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanagent + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman-operator/internal" +) + +type NamespaceProgramReconciler struct { + ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList] +} + +// createBpfProgram moves some shared logic for building bpfProgram objects +// into a central location. +func (r *NamespaceProgramReconciler) createBpfProgram( + attachPoint string, + rec bpfmanReconciler[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList], + annotations map[string]string, +) (*bpfmaniov1alpha1.BpfNsProgram, error) { + + r.Logger.V(1).Info("createBpfNsProgram()", "Name", attachPoint, + "Owner", rec.getOwner().GetName(), "OwnerType", rec.getRecType(), "Name", rec.getName(), "Namespace", rec.getNamespace()) + + if annotations == nil { + annotations = make(map[string]string) + } + annotations[internal.BpfProgramAttachPoint] = attachPoint + + bpfProg := &bpfmaniov1alpha1.BpfNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: generateUniqueName(rec.getName()), + Namespace: rec.getNamespace(), + Finalizers: []string{rec.getFinalizer()}, + Labels: map[string]string{ + internal.BpfProgramOwner: rec.getOwner().GetName(), + internal.AppProgramId: rec.getAppProgramId(), + internal.K8sHostLabel: r.NodeName, + }, + Annotations: annotations, + }, + Spec: bpfmaniov1alpha1.BpfProgramSpec{ + Type: rec.getRecType(), + }, + Status: bpfmaniov1alpha1.BpfProgramStatus{Conditions: []metav1.Condition{}}, + } + + // Make the corresponding BpfProgramConfig the owner + if err := ctrl.SetControllerReference(rec.getOwner(), bpfProg, r.Scheme); err != nil { + return nil, fmt.Errorf("failed to bpfProgram object owner reference: %v", err) + } + + return bpfProg, nil +} + +func (r *NamespaceProgramReconciler) getBpfList( + ctx context.Context, + opts []client.ListOption, +) (*bpfmaniov1alpha1.BpfNsProgramList, error) { + + bpfProgramList := &bpfmaniov1alpha1.BpfNsProgramList{} + + err := r.List(ctx, bpfProgramList, opts...) + if err != nil { + return nil, err + } + + return bpfProgramList, nil +} + +func (r *NamespaceProgramReconciler) updateBpfStatus( + ctx context.Context, + bpfProgram *bpfmaniov1alpha1.BpfNsProgram, + condition metav1.Condition, +) error { + bpfProgram.Status.Conditions = nil + meta.SetStatusCondition(&bpfProgram.Status.Conditions, condition) + return r.Status().Update(ctx, bpfProgram) +} diff --git a/controllers/bpfman-agent/containers.go b/controllers/bpfman-agent/containers.go index c02f9de58..ebdf494fc 100644 --- a/controllers/bpfman-agent/containers.go +++ b/controllers/bpfman-agent/containers.go @@ -26,8 +26,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - "github.com/bpfman/bpfman-operator/internal" "github.com/buger/jsonparser" "github.com/go-logr/logr" ) @@ -42,7 +40,10 @@ type ContainerInfo struct { // should be attached so we can mock it in unit tests. type ContainerGetter interface { // Get the list of containers on this node that match the containerSelector. - GetContainers(ctx context.Context, containerSelector *bpfmaniov1alpha1.ContainerSelector, + GetContainers(ctx context.Context, + selectorNamespace string, + selectorPods metav1.LabelSelector, + selectorContainerNames *[]string, logger logr.Logger) (*[]ContainerInfo, error) } @@ -67,17 +68,19 @@ func NewRealContainerGetter(nodeName string) (*RealContainerGetter, error) { func (c *RealContainerGetter) GetContainers( ctx context.Context, - containerSelector *bpfmaniov1alpha1.ContainerSelector, + selectorNamespace string, + selectorPods metav1.LabelSelector, + selectorContainerNames *[]string, logger logr.Logger) (*[]ContainerInfo, error) { // Get the list of pods that match the selector. - podList, err := c.getPodsForNode(ctx, containerSelector) + podList, err := c.getPodsForNode(ctx, selectorNamespace, selectorPods) if err != nil { return nil, fmt.Errorf("failed to get pod list: %v", err) } // Get the list of containers in the list of pods that match the selector. - containerList, err := getContainerInfo(podList, containerSelector.ContainerNames, logger) + containerList, err := getContainerInfo(podList, selectorContainerNames, logger) if err != nil { return nil, fmt.Errorf("failed to get container info: %v", err) } @@ -89,10 +92,13 @@ func (c *RealContainerGetter) GetContainers( // getPodsForNode returns a list of pods on the given node that match the given // container selector. -func (c *RealContainerGetter) getPodsForNode(ctx context.Context, - containerSelector *bpfmaniov1alpha1.ContainerSelector) (*v1.PodList, error) { +func (c *RealContainerGetter) getPodsForNode( + ctx context.Context, + selectorNamespace string, + selectorPods metav1.LabelSelector, +) (*v1.PodList, error) { - selectorString := metav1.FormatLabelSelector(&containerSelector.Pods) + selectorString := metav1.FormatLabelSelector(&selectorPods) if selectorString == "" { return nil, fmt.Errorf("error parsing selector: %v", selectorString) @@ -106,7 +112,7 @@ func (c *RealContainerGetter) getPodsForNode(ctx context.Context, listOptions.LabelSelector = selectorString } - podList, err := c.clientSet.CoreV1().Pods(containerSelector.Namespace).List(ctx, listOptions) + podList, err := c.clientSet.CoreV1().Pods(selectorNamespace).List(ctx, listOptions) if err != nil { return nil, fmt.Errorf("error getting pod list: %v", err) } @@ -231,12 +237,13 @@ func getContainerInfo(podList *v1.PodList, containerNames *[]string, logger logr // Check if the annotation is set to indicate that no containers on this node // matched the container selector. -func noContainersOnNode(bpfProgram *bpfmaniov1alpha1.BpfProgram) bool { +func noContainersOnNode[T BpfProg](bpfProgram *T, annotationIndex string) bool { if bpfProgram == nil { return false } - noContainersOnNode, ok := bpfProgram.Annotations[internal.UprobeNoContainersOnNode] + annotations := (*bpfProgram).GetAnnotations() + noContainersOnNode, ok := annotations[annotationIndex] if ok && noContainersOnNode == "true" { return true } diff --git a/controllers/bpfman-agent/fentry-program.go b/controllers/bpfman-agent/fentry-program.go index 02545224c..4a6fed9c8 100644 --- a/controllers/bpfman-agent/fentry-program.go +++ b/controllers/bpfman-agent/fentry-program.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanagent import ( @@ -39,7 +41,7 @@ import ( // BpfProgramReconciler reconciles a BpfProgram object type FentryProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler currentFentryProgram *bpfmaniov1alpha1.FentryProgram ourNode *v1.Node } @@ -68,6 +70,14 @@ func (r *FentryProgramReconciler) getName() string { return r.currentFentryProgram.Name } +func (r *FentryProgramReconciler) getNamespace() string { + return r.currentFentryProgram.Namespace +} + +func (r *FentryProgramReconciler) getNoContAnnotationIndex() string { + return internal.FentryNoContainersOnNode +} + func (r *FentryProgramReconciler) getNode() *v1.Node { return r.ourNode } diff --git a/controllers/bpfman-agent/fentry-program_test.go b/controllers/bpfman-agent/fentry-program_test.go index 1a5384162..a2a4e0c52 100644 --- a/controllers/bpfman-agent/fentry-program_test.go +++ b/controllers/bpfman-agent/fentry-program_test.go @@ -24,7 +24,6 @@ import ( "google.golang.org/protobuf/testing/protocmp" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" internal "github.com/bpfman/bpfman-operator/internal" testutils "github.com/bpfman/bpfman-operator/internal/test-utils" @@ -92,18 +91,22 @@ func TestFentryProgramControllerCreate(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &FentryProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &FentryProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -121,7 +124,7 @@ func TestFentryProgramControllerCreate(t *testing.T) { } // Check the BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.NotEmpty(t, bpfProg) @@ -170,11 +173,11 @@ func TestFentryProgramControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) // prog ID should already have been set - id, err := bpfmanagentinternal.GetID(bpfProg) + id, err := GetID(bpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly Built @@ -194,7 +197,7 @@ func TestFentryProgramControllerCreate(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) diff --git a/controllers/bpfman-agent/fexit-program.go b/controllers/bpfman-agent/fexit-program.go index f1799d80a..2690aaf7b 100644 --- a/controllers/bpfman-agent/fexit-program.go +++ b/controllers/bpfman-agent/fexit-program.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanagent import ( @@ -39,7 +41,7 @@ import ( // BpfProgramReconciler reconciles a BpfProgram object type FexitProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler currentFexitProgram *bpfmaniov1alpha1.FexitProgram ourNode *v1.Node } @@ -68,6 +70,14 @@ func (r *FexitProgramReconciler) getName() string { return r.currentFexitProgram.Name } +func (r *FexitProgramReconciler) getNamespace() string { + return r.currentFexitProgram.Namespace +} + +func (r *FexitProgramReconciler) getNoContAnnotationIndex() string { + return internal.FexitNoContainersOnNode +} + func (r *FexitProgramReconciler) getNode() *v1.Node { return r.ourNode } diff --git a/controllers/bpfman-agent/fexit-program_test.go b/controllers/bpfman-agent/fexit-program_test.go index a424384d5..87812a029 100644 --- a/controllers/bpfman-agent/fexit-program_test.go +++ b/controllers/bpfman-agent/fexit-program_test.go @@ -24,7 +24,6 @@ import ( "google.golang.org/protobuf/testing/protocmp" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" internal "github.com/bpfman/bpfman-operator/internal" testutils "github.com/bpfman/bpfman-operator/internal/test-utils" @@ -92,18 +91,21 @@ func TestFexitProgramControllerCreate(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &FexitProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &FexitProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -121,7 +123,7 @@ func TestFexitProgramControllerCreate(t *testing.T) { } // Check the BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.NotEmpty(t, bpfProg) @@ -170,11 +172,11 @@ func TestFexitProgramControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) // prog ID should already have been set - id, err := bpfmanagentinternal.GetID(bpfProg) + id, err := GetID(bpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly Built @@ -194,7 +196,7 @@ func TestFexitProgramControllerCreate(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) diff --git a/controllers/bpfman-agent/internal/bpfman-core.go b/controllers/bpfman-agent/internal/bpfman-core.go index 864e453b3..b068a9d87 100644 --- a/controllers/bpfman-agent/internal/bpfman-core.go +++ b/controllers/bpfman-agent/internal/bpfman-core.go @@ -19,7 +19,6 @@ package internal import ( "context" "fmt" - "strconv" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" "github.com/bpfman/bpfman-operator/internal" @@ -147,7 +146,7 @@ func ListBpfmanPrograms(ctx context.Context, bpfmanClient gobpfman.BpfmanClient, if uuid, ok := metadata[internal.UuidMetadataKey]; ok { out[uuid] = result } else { - return nil, fmt.Errorf("Unable to get uuid from program metadata") + return nil, fmt.Errorf("unable to get uuid from program metadata") } } } @@ -205,18 +204,3 @@ func Build_kernel_info_annotations(p *gobpfman.ListResponse_ListResult) map[stri } return nil } - -// get the program ID from a bpfProgram -func GetID(p *bpfmaniov1alpha1.BpfProgram) (*uint32, error) { - idString, ok := p.Annotations[internal.IdAnnotation] - if !ok { - return nil, fmt.Errorf("failed to get program ID because no annotations") - } - - id, err := strconv.ParseUint(idString, 10, 32) - if err != nil { - return nil, fmt.Errorf("failed to parse program ID: %v", err) - } - uid := uint32(id) - return &uid, nil -} diff --git a/controllers/bpfman-agent/kprobe-program.go b/controllers/bpfman-agent/kprobe-program.go index 8b18400b0..fb8b9fa82 100644 --- a/controllers/bpfman-agent/kprobe-program.go +++ b/controllers/bpfman-agent/kprobe-program.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanagent import ( @@ -39,7 +41,7 @@ import ( // BpfProgramReconciler reconciles a BpfProgram object type KprobeProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler currentKprobeProgram *bpfmaniov1alpha1.KprobeProgram ourNode *v1.Node } @@ -68,6 +70,14 @@ func (r *KprobeProgramReconciler) getName() string { return r.currentKprobeProgram.Name } +func (r *KprobeProgramReconciler) getNamespace() string { + return r.currentKprobeProgram.Namespace +} + +func (r *KprobeProgramReconciler) getNoContAnnotationIndex() string { + return internal.KprobeNoContainersOnNode +} + func (r *KprobeProgramReconciler) getNode() *v1.Node { return r.ourNode } diff --git a/controllers/bpfman-agent/kprobe-program_test.go b/controllers/bpfman-agent/kprobe-program_test.go index 96f1a3744..7c477b1be 100644 --- a/controllers/bpfman-agent/kprobe-program_test.go +++ b/controllers/bpfman-agent/kprobe-program_test.go @@ -24,7 +24,6 @@ import ( "google.golang.org/protobuf/testing/protocmp" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" internal "github.com/bpfman/bpfman-operator/internal" testutils "github.com/bpfman/bpfman-operator/internal/test-utils" @@ -97,18 +96,21 @@ func TestKprobeProgramControllerCreate(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &KprobeProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &KprobeProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -126,7 +128,7 @@ func TestKprobeProgramControllerCreate(t *testing.T) { } // Check the BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.NotEmpty(t, bpfProg) @@ -178,11 +180,11 @@ func TestKprobeProgramControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) // prog ID should already have been set - id, err := bpfmanagentinternal.GetID(bpfProg) + id, err := GetID(bpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly Built @@ -202,7 +204,7 @@ func TestKprobeProgramControllerCreate(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) diff --git a/controllers/bpfman-agent/tc-ns-program.go b/controllers/bpfman-agent/tc-ns-program.go new file mode 100644 index 000000000..f0d9a136c --- /dev/null +++ b/controllers/bpfman-agent/tc-ns-program.go @@ -0,0 +1,299 @@ +/* +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. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanagent + +import ( + "context" + "fmt" + "strconv" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" + "github.com/bpfman/bpfman-operator/internal" + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=tcnsprograms,verbs=get;list;watch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=tcnsprograms,verbs=get;list;watch + +// TcNsProgramReconciler reconciles a TcNsProgram object by creating multiple +// BpfNsProgram objects and managing bpfman for each one. +type TcNsProgramReconciler struct { + NamespaceProgramReconciler + currentTcNsProgram *bpfmaniov1alpha1.TcNsProgram + interfaces []string + ourNode *v1.Node +} + +func (r *TcNsProgramReconciler) getFinalizer() string { + return r.finalizer +} + +func (r *TcNsProgramReconciler) getOwner() metav1.Object { + if r.appOwner == nil { + return r.currentTcNsProgram + } else { + return r.appOwner + } +} + +func (r *TcNsProgramReconciler) getRecType() string { + return r.recType +} + +func (r *TcNsProgramReconciler) getProgType() internal.ProgramType { + return internal.Tc +} + +func (r *TcNsProgramReconciler) getName() string { + return r.currentTcNsProgram.Name +} + +func (r *TcNsProgramReconciler) getNamespace() string { + return r.currentTcNsProgram.Namespace +} + +func (r *TcNsProgramReconciler) getNoContAnnotationIndex() string { + return internal.TcNsNoContainersOnNode +} + +func (r *TcNsProgramReconciler) getNode() *v1.Node { + return r.ourNode +} + +func (r *TcNsProgramReconciler) getBpfProgramCommon() *bpfmaniov1alpha1.BpfProgramCommon { + return &r.currentTcNsProgram.Spec.BpfProgramCommon +} + +func (r *TcNsProgramReconciler) getNodeSelector() *metav1.LabelSelector { + return &r.currentTcNsProgram.Spec.NodeSelector +} + +func (r *TcNsProgramReconciler) getBpfGlobalData() map[string][]byte { + return r.currentTcNsProgram.Spec.GlobalData +} + +func (r *TcNsProgramReconciler) getAppProgramId() string { + return appProgramId(r.currentTcNsProgram.GetLabels()) +} + +func (r *TcNsProgramReconciler) setCurrentProgram(program client.Object) error { + var err error + var ok bool + + r.currentTcNsProgram, ok = program.(*bpfmaniov1alpha1.TcNsProgram) + if !ok { + return fmt.Errorf("failed to cast program to TcNsProgram") + } + + r.interfaces, err = getInterfaces(&r.currentTcNsProgram.Spec.InterfaceSelector, r.ourNode) + if err != nil { + return fmt.Errorf("failed to get interfaces for TcNsProgram: %v", err) + } + + return nil +} + +// SetupWithManager sets up the controller with the Manager. +// The Bpfman-Agent should reconcile whenever a TcNsProgram is updated, +// load the program to the node via bpfman, and then create BpfNsProgram object(s) +// to reflect per node state information. +func (r *TcNsProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.TcNsProgram{}, builder.WithPredicates(predicate.And( + predicate.GenerationChangedPredicate{}, + predicate.ResourceVersionChangedPredicate{}), + ), + ). + Owns(&bpfmaniov1alpha1.BpfNsProgram{}, + builder.WithPredicates(predicate.And( + internal.BpfNsProgramTypePredicate(internal.Tc.String()), + internal.BpfProgramNodePredicate(r.NodeName)), + ), + ). + // Only trigger reconciliation if node labels change since that could + // make the TcNsProgram no longer select the Node. Additionally only + // care about events specific to our node + Watches( + &v1.Node{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And(predicate.LabelChangedPredicate{}, nodePredicate(r.NodeName))), + ). + // Watch for changes in Pod resources in case we are using a container selector. + Watches( + &v1.Pod{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(podOnNodePredicate(r.NodeName)), + ). + Complete(r) +} + +func (r *TcNsProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfNsProgramList, error) { + progs := &bpfmaniov1alpha1.BpfNsProgramList{} + + // There is a container selector, so see if there are any matching + // containers on this node. + containerInfo, err := r.Containers.GetContainers( + ctx, + r.getNamespace(), + r.currentTcNsProgram.Spec.Containers.Pods, + r.currentTcNsProgram.Spec.Containers.ContainerNames, + r.Logger, + ) + if err != nil { + return nil, fmt.Errorf("failed to get container pids: %v", err) + } + + if containerInfo == nil || len(*containerInfo) == 0 { + // There were no errors, but the container selector didn't + // select any containers on this node. + for _, iface := range r.interfaces { + attachPoint := fmt.Sprintf("%s-%s-%s", + iface, + r.currentTcNsProgram.Spec.Direction, + "no-containers-on-node", + ) + + annotations := map[string]string{ + internal.TcNsProgramInterface: iface, + internal.TcNsNoContainersOnNode: "true", + } + + prog, err := r.createBpfProgram(attachPoint, r, annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfNsProgram %s: %v", attachPoint, err) + } + + progs.Items = append(progs.Items, *prog) + } + } else { + // Containers were found, so create BpfNsPrograms. + for i := range *containerInfo { + container := (*containerInfo)[i] + for _, iface := range r.interfaces { + attachPoint := fmt.Sprintf("%s-%s-%s-%s", + iface, + r.currentTcNsProgram.Spec.Direction, + container.podName, + container.containerName, + ) + + annotations := map[string]string{ + internal.TcNsProgramInterface: iface, + internal.TcNsContainerPid: strconv.FormatInt(container.pid, 10), + } + + prog, err := r.createBpfProgram(attachPoint, r, annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfNsProgram %s: %v", attachPoint, err) + } + + progs.Items = append(progs.Items, *prog) + } + } + } + + return progs, nil +} + +func (r *TcNsProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // Initialize node and current program + r.currentTcNsProgram = &bpfmaniov1alpha1.TcNsProgram{} + r.finalizer = internal.TcNsProgramControllerFinalizer + r.recType = internal.Tc.String() + r.ourNode = &v1.Node{} + r.Logger = ctrl.Log.WithName("tc-ns") + + r.Logger.Info("bpfman-agent enter: tc-ns", "Namespace", req.Namespace, "Name", req.Name) + + // Lookup K8s node object for this bpfman-agent This should always succeed + if err := r.Get(ctx, types.NamespacedName{Namespace: v1.NamespaceAll, Name: r.NodeName}, r.ourNode); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfman-agent node %s : %v", + req.NamespacedName, err) + } + + tcPrograms := &bpfmaniov1alpha1.TcNsProgramList{} + + opts := []client.ListOption{} + + if err := r.List(ctx, tcPrograms, opts...); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting TcNsPrograms for full reconcile %s : %v", + req.NamespacedName, err) + } + + if len(tcPrograms.Items) == 0 { + r.Logger.Info("TcNsProgramController found no TC Programs") + return ctrl.Result{Requeue: false}, nil + } + + // Create a list of tc programs to pass into reconcileCommon() + var tcObjects []client.Object = make([]client.Object, len(tcPrograms.Items)) + for i := range tcPrograms.Items { + tcObjects[i] = &tcPrograms.Items[i] + } + + // Reconcile each TcNsProgram. + _, result, err := r.reconcileCommon(ctx, r, tcObjects) + return result, err +} + +func (r *TcNsProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfNsProgram, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) { + bytecode, err := bpfmanagentinternal.GetBytecode(r.Client, &r.currentTcNsProgram.Spec.ByteCode) + if err != nil { + return nil, fmt.Errorf("failed to process bytecode selector: %v", err) + } + + attachInfo := &gobpfman.TCAttachInfo{ + Priority: r.currentTcNsProgram.Spec.Priority, + Iface: bpfProgram.Annotations[internal.TcNsProgramInterface], + Direction: r.currentTcNsProgram.Spec.Direction, + ProceedOn: tcProceedOnToInt(r.currentTcNsProgram.Spec.ProceedOn), + } + + containerPidStr, ok := bpfProgram.Annotations[internal.TcNsContainerPid] + if ok { + netns := fmt.Sprintf("/host/proc/%s/ns/net", containerPidStr) + attachInfo.Netns = &netns + } + + loadRequest := gobpfman.LoadRequest{ + Bytecode: bytecode, + Name: r.currentTcNsProgram.Spec.BpfFunctionName, + ProgramType: uint32(internal.Tc), + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_TcAttachInfo{ + TcAttachInfo: attachInfo, + }, + }, + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.getOwner().GetName()}, + GlobalData: r.currentTcNsProgram.Spec.GlobalData, + MapOwnerId: mapOwnerId, + } + + return &loadRequest, nil +} diff --git a/controllers/bpfman-agent/tc-ns-program_test.go b/controllers/bpfman-agent/tc-ns-program_test.go new file mode 100644 index 000000000..3ed6a3df5 --- /dev/null +++ b/controllers/bpfman-agent/tc-ns-program_test.go @@ -0,0 +1,556 @@ +/* +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 bpfmanagent + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + + internal "github.com/bpfman/bpfman-operator/internal" + + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestTcNsProgramControllerCreate(t *testing.T) { + var ( + name = "fakeTcNsProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + direction = "ingress" + fakeNode = testutils.NewNode("fake-control-plane") + fakePodName = "my-pod" + fakeContainerName = "my-container-1" + fakePid = int64(20984) + fakeInt = "eth0" + ctx = context.TODO() + appProgramId = "" + bpfProg = &bpfmaniov1alpha1.BpfNsProgram{} + fakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81" + attachPoint = fmt.Sprintf("%s-%s-%s-%s", + fakeInt, + direction, + fakePodName, + fakeContainerName, + ) + ) + // A TcNsProgram object with metadata and spec. + tc := &bpfmaniov1alpha1.TcNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: bpfmaniov1alpha1.TcNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + TcNsProgramInfo: bpfmaniov1alpha1.TcNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &[]string{fakeInt}, + }, + Priority: 0, + Direction: direction, + ProceedOn: []bpfmaniov1alpha1.TcProceedOnValue{ + bpfmaniov1alpha1.TcProceedOnValue("pipe"), + bpfmaniov1alpha1.TcProceedOnValue("dispatcher_return"), + }, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": fakePodName, + }, + }, + ContainerNames: &[]string{fakeContainerName}, + }, + }, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, tc} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, tc) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.TcNsProgramList{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(tc).WithStatusSubresource(&bpfmaniov1alpha1.BpfNsProgram{}).WithRuntimeObjects(objs...).Build() + + cli := agenttestutils.NewBpfmanClientFake() + + testContainers := FakeContainerGetter{ + containerList: &[]ContainerInfo{ + { + podName: fakePodName, + containerName: fakeContainerName, + pid: fakePid, + }, + }, + } + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + BpfmanClient: cli, + NodeName: fakeNode.Name, + Containers: &testContainers, + } + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &TcNsProgramReconciler{NamespaceProgramReconciler: npr, ourNode: fakeNode} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should create the bpf program object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Check the BpfNsProgram Object was created successfully + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) + require.NoError(t, err) + + require.NotEmpty(t, bpfProg) + // owningConfig Label was correctly set + require.Equal(t, bpfProg.Labels[internal.BpfProgramOwner], name) + // node Label was correctly set + require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProg.Finalizers[0]) + // Type is set + require.Equal(t, r.getRecType(), bpfProg.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of BpfNsProgram with Fake UID since the fake API server won't + bpfProg.UID = types.UID(fakeUID) + err = cl.Update(ctx, bpfProg) + require.NoError(t, err) + + // Second reconcile should create the bpfman Load Request and update the + // BpfNsProgram object's 'Programs' field. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + uuid := string(bpfProg.UID) + netns := fmt.Sprintf("/host/proc/%d/ns/net", fakePid) + + expectedLoadReq := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Tc.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(uuid), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_TcAttachInfo{ + TcAttachInfo: &gobpfman.TCAttachInfo{ + Iface: fakeInt, + Priority: 0, + Direction: direction, + ProceedOn: []int32{3, 30}, + Netns: &netns, + }, + }, + }, + } + + // Check that the BpfNsProgram's programs was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) + require.NoError(t, err) + + // prog ID should already have been set + id, err := GetID(bpfProg) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly built + if !cmp.Equal(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) { + t.Logf("Diff %v", cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Third reconcile should update the BpfNsPrograms status to loaded + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check that the BpfNsProgram's status was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) +} + +func TestTcNsProgramControllerCreateMultiIntf(t *testing.T) { + var ( + name = "fakeTcNsProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + direction = "ingress" + fakeNode = testutils.NewNode("fake-control-plane") + fakePodName = "my-pod" + fakeContainerName = "my-container-1" + fakePid = int64(391) + fakeInts = []string{"eth0", "eth1"} + ctx = context.TODO() + appProgramId0 = "" + appProgramId1 = "" + bpfProgEth0 = &bpfmaniov1alpha1.BpfNsProgram{} + bpfProgEth1 = &bpfmaniov1alpha1.BpfNsProgram{} + fakeUID0 = "ef71d42c-aa21-48e8-a697-82391d801a80" + fakeUID1 = "ef71d42c-aa21-48e8-a697-82391d801a81" + attachPoint0 = fmt.Sprintf("%s-%s-%s-%s", + fakeInts[0], + direction, + fakePodName, + fakeContainerName, + ) + attachPoint1 = fmt.Sprintf("%s-%s-%s-%s", + fakeInts[1], + direction, + fakePodName, + fakeContainerName, + ) + ) + // A TcNsProgram object with metadata and spec. + tc := &bpfmaniov1alpha1.TcNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: bpfmaniov1alpha1.TcNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + TcNsProgramInfo: bpfmaniov1alpha1.TcNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &fakeInts, + }, + Priority: 0, + Direction: direction, + ProceedOn: []bpfmaniov1alpha1.TcProceedOnValue{ + bpfmaniov1alpha1.TcProceedOnValue("pipe"), + bpfmaniov1alpha1.TcProceedOnValue("dispatcher_return"), + }, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": fakePodName, + }, + }, + }, + }, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, tc} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, tc) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(tc).WithStatusSubresource(&bpfmaniov1alpha1.BpfNsProgram{}).WithRuntimeObjects(objs...).Build() + + cli := agenttestutils.NewBpfmanClientFake() + + testContainers := FakeContainerGetter{ + containerList: &[]ContainerInfo{ + { + podName: fakePodName, + containerName: fakeContainerName, + pid: fakePid, + }, + }, + } + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + BpfmanClient: cli, + NodeName: fakeNode.Name, + Containers: &testContainers, + } + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &TcNsProgramReconciler{NamespaceProgramReconciler: npr, ourNode: fakeNode} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should create the first bpf program object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Check the first BpfNsProgram Object was created successfully + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + require.NotEmpty(t, bpfProgEth0) + // owningConfig Label was correctly set + require.Equal(t, bpfProgEth0.Labels[internal.BpfProgramOwner], name) + // node Label was correctly set + require.Equal(t, bpfProgEth0.Labels[internal.K8sHostLabel], fakeNode.Name) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProgEth0.Finalizers[0]) + // Type is set + require.Equal(t, r.getRecType(), bpfProgEth0.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of BpfNsProgram with Fake UID since the fake API server won't + bpfProgEth0.UID = types.UID(fakeUID0) + err = cl.Update(ctx, bpfProgEth0) + require.NoError(t, err) + + // Second reconcile should create the bpfman Load Requests for the first BpfNsProgram and update the prog id. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Third reconcile should set the second bpf program object's status. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Fourth reconcile should create the bpfman Load Requests for the second BpfNsProgram. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Require no requeue + require.False(t, res.Requeue) + + // Check the Second BpfNsProgram Object was created successfully + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + require.NotEmpty(t, bpfProgEth1) + // owningConfig Label was correctly set + require.Equal(t, bpfProgEth1.Labels[internal.BpfProgramOwner], name) + // node Label was correctly set + require.Equal(t, bpfProgEth1.Labels[internal.K8sHostLabel], fakeNode.Name) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProgEth1.Finalizers[0]) + // Type is set + require.Equal(t, r.getRecType(), bpfProgEth1.Spec.Type) + + // Update UID of BpfNsProgram with Fake UID since the fake API server won't + bpfProgEth1.UID = types.UID(fakeUID1) + err = cl.Update(ctx, bpfProgEth1) + require.NoError(t, err) + + // Fifth reconcile should create the second BpfNsProgram. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Sixth reconcile should update the second BpfNsProgram's status. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + uuid0 := string(bpfProgEth0.UID) + netns := fmt.Sprintf("/host/proc/%d/ns/net", fakePid) + + expectedLoadReq0 := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Tc.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(uuid0), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_TcAttachInfo{ + TcAttachInfo: &gobpfman.TCAttachInfo{ + Iface: fakeInts[0], + Priority: 0, + Direction: direction, + ProceedOn: []int32{3, 30}, + Netns: &netns, + }, + }, + }, + } + + uuid1 := string(bpfProgEth1.UID) + + expectedLoadReq1 := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Tc.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(uuid1), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_TcAttachInfo{ + TcAttachInfo: &gobpfman.TCAttachInfo{ + Iface: fakeInts[1], + Priority: 0, + Direction: direction, + ProceedOn: []int32{3, 30}, + Netns: &netns, + }, + }, + }, + } + + // Check that the BpfNsProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + // prog ID should already have been set + id0, err := GetID(bpfProgEth0) + require.NoError(t, err) + + // Check that the BpfNsProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + // prog ID should already have been set + id1, err := GetID(bpfProgEth1) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly built + if !cmp.Equal(expectedLoadReq0, cli.LoadRequests[int(*id0)], protocmp.Transform()) { + t.Logf("Diff %v", cmp.Diff(expectedLoadReq0, cli.LoadRequests[int(*id0)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Check the bpfLoadRequest was correctly built + if !cmp.Equal(expectedLoadReq1, cli.LoadRequests[int(*id1)], protocmp.Transform()) { + t.Logf("Diff %v", cmp.Diff(expectedLoadReq1, cli.LoadRequests[int(*id1)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Check that the BpfNsProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + // Check that the BpfNsProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + // Third reconcile should update the BpfNsPrograms status to loaded + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check that the BpfNsProgram's status was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth0.Status.Conditions[0].Type) + + // Check that the BpfNsProgram's status was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth1.Status.Conditions[0].Type) +} diff --git a/controllers/bpfman-agent/tc-program.go b/controllers/bpfman-agent/tc-program.go index d65134e06..7baff7874 100644 --- a/controllers/bpfman-agent/tc-program.go +++ b/controllers/bpfman-agent/tc-program.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanagent import ( @@ -41,7 +43,7 @@ import ( // TcProgramReconciler reconciles a tcProgram object by creating multiple // bpfProgram objects and managing bpfman for each one. type TcProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler currentTcProgram *bpfmaniov1alpha1.TcProgram interfaces []string ourNode *v1.Node @@ -71,6 +73,14 @@ func (r *TcProgramReconciler) getName() string { return r.currentTcProgram.Name } +func (r *TcProgramReconciler) getNamespace() string { + return r.currentTcProgram.Namespace +} + +func (r *TcProgramReconciler) getNoContAnnotationIndex() string { + return internal.TcNoContainersOnNode +} + func (r *TcProgramReconciler) getNode() *v1.Node { return r.ourNode } @@ -183,7 +193,13 @@ func (r *TcProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpfm // There is a container selector, so see if there are any matching // containers on this node. - containerInfo, err := r.Containers.GetContainers(ctx, r.currentTcProgram.Spec.Containers, r.Logger) + containerInfo, err := r.Containers.GetContainers( + ctx, + r.currentTcProgram.Spec.Containers.Namespace, + r.currentTcProgram.Spec.Containers.Pods, + r.currentTcProgram.Spec.Containers.ContainerNames, + r.Logger, + ) if err != nil { return nil, fmt.Errorf("failed to get container pids: %v", err) } @@ -261,7 +277,7 @@ func (r *TcProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( r.ourNode = &v1.Node{} r.Logger = ctrl.Log.WithName("tc") - r.Logger.Info("bpfman-agent enter: TC", "Name", req.Name) + r.Logger.Info("bpfman-agent enter: tc", "Name", req.Name) // Lookup K8s node object for this bpfman-agent This should always succeed if err := r.Get(ctx, types.NamespacedName{Namespace: v1.NamespaceAll, Name: r.NodeName}, r.ourNode); err != nil { diff --git a/controllers/bpfman-agent/tc-program_test.go b/controllers/bpfman-agent/tc-program_test.go index a1b711843..f966b2a08 100644 --- a/controllers/bpfman-agent/tc-program_test.go +++ b/controllers/bpfman-agent/tc-program_test.go @@ -24,7 +24,6 @@ import ( "google.golang.org/protobuf/testing/protocmp" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" testutils "github.com/bpfman/bpfman-operator/internal/test-utils" @@ -102,18 +101,21 @@ func TestTcProgramControllerCreate(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &TcProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &TcProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -131,7 +133,7 @@ func TestTcProgramControllerCreate(t *testing.T) { } // Check the BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.NotEmpty(t, bpfProg) @@ -183,11 +185,11 @@ func TestTcProgramControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) // prog ID should already have been set - id, err := bpfmanagentinternal.GetID(bpfProg) + id, err := GetID(bpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly built @@ -206,7 +208,7 @@ func TestTcProgramControllerCreate(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) @@ -274,18 +276,21 @@ func TestTcProgramControllerCreateMultiIntf(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &TcProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &TcProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -303,7 +308,7 @@ func TestTcProgramControllerCreateMultiIntf(t *testing.T) { } // Check the first BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) require.NotEmpty(t, bpfProgEth0) @@ -351,7 +356,7 @@ func TestTcProgramControllerCreateMultiIntf(t *testing.T) { require.False(t, res.Requeue) // Check the Second BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) require.NotEmpty(t, bpfProgEth1) @@ -429,19 +434,19 @@ func TestTcProgramControllerCreateMultiIntf(t *testing.T) { } // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) // prog ID should already have been set - id0, err := bpfmanagentinternal.GetID(bpfProgEth0) + id0, err := GetID(bpfProgEth0) require.NoError(t, err) // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) // prog ID should already have been set - id1, err := bpfmanagentinternal.GetID(bpfProgEth1) + id1, err := GetID(bpfProgEth1) require.NoError(t, err) // Check the bpfLoadRequest was correctly built @@ -457,11 +462,11 @@ func TestTcProgramControllerCreateMultiIntf(t *testing.T) { } // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) // Third reconcile should update the bpfPrograms status to loaded @@ -474,13 +479,13 @@ func TestTcProgramControllerCreateMultiIntf(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth0.Status.Conditions[0].Type) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth1.Status.Conditions[0].Type) diff --git a/controllers/bpfman-agent/tcx-ns-program.go b/controllers/bpfman-agent/tcx-ns-program.go new file mode 100644 index 000000000..7546fb7ba --- /dev/null +++ b/controllers/bpfman-agent/tcx-ns-program.go @@ -0,0 +1,298 @@ +/* +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. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanagent + +import ( + "context" + "fmt" + "strconv" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" + "github.com/bpfman/bpfman-operator/internal" + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=tcxnsprograms,verbs=get;list;watch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=tcxnsprograms,verbs=get;list;watch + +// TcxNsProgramReconciler reconciles a tcxNsProgram object by creating multiple +// bpfNsProgram objects and managing bpfman for each one. +type TcxNsProgramReconciler struct { + NamespaceProgramReconciler + currentTcxNsProgram *bpfmaniov1alpha1.TcxNsProgram + interfaces []string + ourNode *v1.Node +} + +func (r *TcxNsProgramReconciler) getFinalizer() string { + return r.finalizer +} + +func (r *TcxNsProgramReconciler) getOwner() metav1.Object { + if r.appOwner == nil { + return r.currentTcxNsProgram + } else { + return r.appOwner + } +} + +func (r *TcxNsProgramReconciler) getRecType() string { + return r.recType +} + +func (r *TcxNsProgramReconciler) getProgType() internal.ProgramType { + return internal.Tc +} + +func (r *TcxNsProgramReconciler) getName() string { + return r.currentTcxNsProgram.Name +} + +func (r *TcxNsProgramReconciler) getNamespace() string { + return r.currentTcxNsProgram.Namespace +} + +func (r *TcxNsProgramReconciler) getNoContAnnotationIndex() string { + return internal.TcxNsNoContainersOnNode +} + +func (r *TcxNsProgramReconciler) getNode() *v1.Node { + return r.ourNode +} + +func (r *TcxNsProgramReconciler) getBpfProgramCommon() *bpfmaniov1alpha1.BpfProgramCommon { + return &r.currentTcxNsProgram.Spec.BpfProgramCommon +} + +func (r *TcxNsProgramReconciler) getNodeSelector() *metav1.LabelSelector { + return &r.currentTcxNsProgram.Spec.NodeSelector +} + +func (r *TcxNsProgramReconciler) getBpfGlobalData() map[string][]byte { + return r.currentTcxNsProgram.Spec.GlobalData +} + +func (r *TcxNsProgramReconciler) getAppProgramId() string { + return appProgramId(r.currentTcxNsProgram.GetLabels()) +} + +func (r *TcxNsProgramReconciler) setCurrentProgram(program client.Object) error { + var err error + var ok bool + + r.currentTcxNsProgram, ok = program.(*bpfmaniov1alpha1.TcxNsProgram) + if !ok { + return fmt.Errorf("failed to cast program to TcxNsProgram") + } + + r.interfaces, err = getInterfaces(&r.currentTcxNsProgram.Spec.InterfaceSelector, r.ourNode) + if err != nil { + return fmt.Errorf("failed to get interfaces for TcxNsProgram: %v", err) + } + + return nil +} + +// SetupWithManager sets up the controller with the Manager. +// The Bpfman-Agent should reconcile whenever a TcxNsProgram is updated, +// load the program to the node via bpfman, and then create bpfNsProgram object(s) +// to reflect per node state information. +func (r *TcxNsProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.TcxNsProgram{}, builder.WithPredicates(predicate.And( + predicate.GenerationChangedPredicate{}, + predicate.ResourceVersionChangedPredicate{}), + ), + ). + Owns(&bpfmaniov1alpha1.BpfNsProgram{}, + builder.WithPredicates(predicate.And( + internal.BpfNsProgramTypePredicate(internal.TcxString), + internal.BpfProgramNodePredicate(r.NodeName)), + ), + ). + // Only trigger reconciliation if node labels change since that could + // make the TcxNsProgram no longer select the Node. Additionally only + // care about events specific to our node + Watches( + &v1.Node{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And(predicate.LabelChangedPredicate{}, nodePredicate(r.NodeName))), + ). + // Watch for changes in Pod resources in case we are using a container selector. + Watches( + &v1.Pod{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(podOnNodePredicate(r.NodeName)), + ). + Complete(r) +} + +func (r *TcxNsProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfNsProgramList, error) { + progs := &bpfmaniov1alpha1.BpfNsProgramList{} + + // There is a container selector, so see if there are any matching + // containers on this node. + containerInfo, err := r.Containers.GetContainers( + ctx, + r.getNamespace(), + r.currentTcxNsProgram.Spec.Containers.Pods, + r.currentTcxNsProgram.Spec.Containers.ContainerNames, + r.Logger, + ) + if err != nil { + return nil, fmt.Errorf("failed to get container pids: %v", err) + } + + if containerInfo == nil || len(*containerInfo) == 0 { + // There were no errors, but the container selector didn't + // select any containers on this node. + for _, iface := range r.interfaces { + attachPoint := fmt.Sprintf("%s-%s-%s", + iface, + r.currentTcxNsProgram.Spec.Direction, + "no-containers-on-node", + ) + + annotations := map[string]string{ + internal.TcxNsProgramInterface: iface, + internal.TcxNsNoContainersOnNode: "true", + } + + prog, err := r.createBpfProgram(attachPoint, r, annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfNsProgram %s: %v", attachPoint, err) + } + + progs.Items = append(progs.Items, *prog) + } + } else { + // Containers were found, so create BpfNsPrograms. + for i := range *containerInfo { + container := (*containerInfo)[i] + for _, iface := range r.interfaces { + attachPoint := fmt.Sprintf("%s-%s-%s-%s", + iface, + r.currentTcxNsProgram.Spec.Direction, + container.podName, + container.containerName, + ) + + annotations := map[string]string{ + internal.TcxNsProgramInterface: iface, + internal.TcxNsContainerPid: strconv.FormatInt(container.pid, 10), + } + + prog, err := r.createBpfProgram(attachPoint, r, annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfNsProgram %s: %v", attachPoint, err) + } + + progs.Items = append(progs.Items, *prog) + } + } + } + + return progs, nil +} + +func (r *TcxNsProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // Initialize node and current program + r.currentTcxNsProgram = &bpfmaniov1alpha1.TcxNsProgram{} + r.finalizer = internal.TcxNsProgramControllerFinalizer + r.recType = internal.TcxString + r.ourNode = &v1.Node{} + r.Logger = ctrl.Log.WithName("tcx-ns") + + r.Logger.Info("bpfman-agent enter: tcx-ns", "Namespace", req.Namespace, "Name", req.Name) + + // Lookup K8s node object for this bpfman-agent This should always succeed + if err := r.Get(ctx, types.NamespacedName{Namespace: v1.NamespaceAll, Name: r.NodeName}, r.ourNode); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfman-agent node %s : %v", + req.NamespacedName, err) + } + + tcxPrograms := &bpfmaniov1alpha1.TcxNsProgramList{} + + opts := []client.ListOption{} + + if err := r.List(ctx, tcxPrograms, opts...); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting TcxNsPrograms for full reconcile %s : %v", + req.NamespacedName, err) + } + + if len(tcxPrograms.Items) == 0 { + r.Logger.Info("TcxNsProgramController found no TCX NS Programs") + return ctrl.Result{Requeue: false}, nil + } + + // Create a list of tcx programs to pass into reconcileCommon() + var tcxObjects []client.Object = make([]client.Object, len(tcxPrograms.Items)) + for i := range tcxPrograms.Items { + tcxObjects[i] = &tcxPrograms.Items[i] + } + + // Reconcile each TcxNsProgram. + _, result, err := r.reconcileCommon(ctx, r, tcxObjects) + return result, err +} + +func (r *TcxNsProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfNsProgram, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) { + bytecode, err := bpfmanagentinternal.GetBytecode(r.Client, &r.currentTcxNsProgram.Spec.ByteCode) + if err != nil { + return nil, fmt.Errorf("failed to process bytecode selector: %v", err) + } + + attachInfo := &gobpfman.TCXAttachInfo{ + Priority: r.currentTcxNsProgram.Spec.Priority, + Iface: bpfProgram.Annotations[internal.TcxNsProgramInterface], + Direction: r.currentTcxNsProgram.Spec.Direction, + } + + containerPidStr, ok := bpfProgram.Annotations[internal.TcxNsContainerPid] + if ok { + netns := fmt.Sprintf("/host/proc/%s/ns/net", containerPidStr) + attachInfo.Netns = &netns + } + + loadRequest := gobpfman.LoadRequest{ + Bytecode: bytecode, + Name: r.currentTcxNsProgram.Spec.BpfFunctionName, + ProgramType: uint32(internal.Tc), + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_TcxAttachInfo{ + TcxAttachInfo: attachInfo, + }, + }, + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.getOwner().GetName()}, + GlobalData: r.currentTcxNsProgram.Spec.GlobalData, + MapOwnerId: mapOwnerId, + } + + return &loadRequest, nil +} diff --git a/controllers/bpfman-agent/tcx-ns-program_test.go b/controllers/bpfman-agent/tcx-ns-program_test.go new file mode 100644 index 000000000..3495407ac --- /dev/null +++ b/controllers/bpfman-agent/tcx-ns-program_test.go @@ -0,0 +1,546 @@ +/* +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 bpfmanagent + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + + internal "github.com/bpfman/bpfman-operator/internal" + + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestTcxNsProgramControllerCreate(t *testing.T) { + var ( + name = "fakeTcxProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + direction = "ingress" + fakeNode = testutils.NewNode("fake-control-plane") + fakePodName = "my-pod" + fakeContainerName = "my-container-1" + fakePid = int64(5457) + fakeInt = "eth0" + ctx = context.TODO() + appProgramId = "" + bpfProg = &bpfmaniov1alpha1.BpfNsProgram{} + fakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81" + attachPoint = fmt.Sprintf("%s-%s-%s-%s", + fakeInt, + direction, + fakePodName, + fakeContainerName, + ) + ) + // A TcxNsProgram object with metadata and spec. + tcx := &bpfmaniov1alpha1.TcxNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: bpfmaniov1alpha1.TcxNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + TcxNsProgramInfo: bpfmaniov1alpha1.TcxNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &[]string{fakeInt}, + }, + Priority: 0, + Direction: direction, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + }, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, tcx} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, tcx) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.TcxNsProgramList{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(tcx).WithStatusSubresource(&bpfmaniov1alpha1.BpfNsProgram{}).WithRuntimeObjects(objs...).Build() + + cli := agenttestutils.NewBpfmanClientFake() + + testContainers := FakeContainerGetter{ + containerList: &[]ContainerInfo{ + { + podName: fakePodName, + containerName: fakeContainerName, + pid: fakePid, + }, + }, + } + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + BpfmanClient: cli, + NodeName: fakeNode.Name, + Containers: &testContainers, + } + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &TcxNsProgramReconciler{NamespaceProgramReconciler: npr, ourNode: fakeNode} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should create the bpf program object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Check the BpfNsProgram Object was created successfully + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) + require.NoError(t, err) + + require.NotEmpty(t, bpfProg) + // owningConfig Label was correctly set + require.Equal(t, bpfProg.Labels[internal.BpfProgramOwner], name) + // node Label was correctly set + require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProg.Finalizers[0]) + // Type is set + require.Equal(t, r.getRecType(), bpfProg.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of BpfNsProgram with Fake UID since the fake API server won't + bpfProg.UID = types.UID(fakeUID) + err = cl.Update(ctx, bpfProg) + require.NoError(t, err) + + // Second reconcile should create the bpfman Load Request and update the + // BpfNsProgram object's 'Programs' field. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + uuid := string(bpfProg.UID) + netns := fmt.Sprintf("/host/proc/%d/ns/net", fakePid) + + expectedLoadReq := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Tc.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(uuid), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_TcxAttachInfo{ + TcxAttachInfo: &gobpfman.TCXAttachInfo{ + Iface: fakeInt, + Priority: tcx.Spec.Priority, + Direction: direction, + Netns: &netns, + }, + }, + }, + } + + // Check that the BpfNsProgram's programs was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) + require.NoError(t, err) + + // prog ID should already have been set + id, err := GetID(bpfProg) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly built + if !cmp.Equal(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) { + t.Logf("Diff %v", cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Third reconcile should update the BpfNsPrograms status to loaded + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check that the BpfNsProgram's status was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) +} + +func TestTcxNsProgramControllerCreateMultiIntf(t *testing.T) { + var ( + name = "fakeTcProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + direction = "ingress" + fakeNode = testutils.NewNode("fake-control-plane") + fakePodName = "my-pod" + fakeContainerName = "my-container-1" + fakePid = int64(9574) + fakeInts = []string{"eth0", "eth1"} + ctx = context.TODO() + appProgramId0 = "" + appProgramId1 = "" + bpfProgEth0 = &bpfmaniov1alpha1.BpfNsProgram{} + bpfProgEth1 = &bpfmaniov1alpha1.BpfNsProgram{} + fakeUID0 = "ef71d42c-aa21-48e8-a697-82391d801a80" + fakeUID1 = "ef71d42c-aa21-48e8-a697-82391d801a81" + attachPoint0 = fmt.Sprintf("%s-%s-%s-%s", + fakeInts[0], + direction, + fakePodName, + fakeContainerName, + ) + attachPoint1 = fmt.Sprintf("%s-%s-%s-%s", + fakeInts[1], + direction, + fakePodName, + fakeContainerName, + ) + ) + // A TcProgram object with metadata and spec. + tcx := &bpfmaniov1alpha1.TcxNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: bpfmaniov1alpha1.TcxNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + TcxNsProgramInfo: bpfmaniov1alpha1.TcxNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &fakeInts, + }, + Priority: 10, + Direction: direction, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + }, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, tcx} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, tcx) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(tcx).WithStatusSubresource(&bpfmaniov1alpha1.BpfNsProgram{}).WithRuntimeObjects(objs...).Build() + + cli := agenttestutils.NewBpfmanClientFake() + + testContainers := FakeContainerGetter{ + containerList: &[]ContainerInfo{ + { + podName: fakePodName, + containerName: fakeContainerName, + pid: fakePid, + }, + }, + } + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + BpfmanClient: cli, + NodeName: fakeNode.Name, + Containers: &testContainers, + } + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &TcxNsProgramReconciler{NamespaceProgramReconciler: npr, ourNode: fakeNode} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should create the first bpf program object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Check the first BpfNsProgram Object was created successfully + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + require.NotEmpty(t, bpfProgEth0) + // owningConfig Label was correctly set + require.Equal(t, bpfProgEth0.Labels[internal.BpfProgramOwner], name) + // node Label was correctly set + require.Equal(t, bpfProgEth0.Labels[internal.K8sHostLabel], fakeNode.Name) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProgEth0.Finalizers[0]) + // Type is set + require.Equal(t, r.getRecType(), bpfProgEth0.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of BpfNsProgram with Fake UID since the fake API server won't + bpfProgEth0.UID = types.UID(fakeUID0) + err = cl.Update(ctx, bpfProgEth0) + require.NoError(t, err) + + // Second reconcile should create the bpfman Load Requests for the first BpfNsProgram and update the prog id. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Third reconcile should set the second bpf program object's status. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Fourth reconcile should create the bpfman Load Requests for the second BpfNsProgram. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Require no requeue + require.False(t, res.Requeue) + + // Check the Second BpfNsProgram Object was created successfully + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + require.NotEmpty(t, bpfProgEth1) + // owningConfig Label was correctly set + require.Equal(t, bpfProgEth1.Labels[internal.BpfProgramOwner], name) + // node Label was correctly set + require.Equal(t, bpfProgEth1.Labels[internal.K8sHostLabel], fakeNode.Name) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProgEth1.Finalizers[0]) + // Type is set + require.Equal(t, r.getRecType(), bpfProgEth1.Spec.Type) + + // Update UID of BpfNsProgram with Fake UID since the fake API server won't + bpfProgEth1.UID = types.UID(fakeUID1) + err = cl.Update(ctx, bpfProgEth1) + require.NoError(t, err) + + // Fifth reconcile should create the second BpfNsProgram. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Sixth reconcile should update the second BpfNsProgram's status. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + uuid0 := string(bpfProgEth0.UID) + netns := fmt.Sprintf("/host/proc/%d/ns/net", fakePid) + + expectedLoadReq0 := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Tc.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(uuid0), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_TcxAttachInfo{ + TcxAttachInfo: &gobpfman.TCXAttachInfo{ + Iface: fakeInts[0], + Priority: tcx.Spec.Priority, + Direction: direction, + Netns: &netns, + }, + }, + }, + } + + uuid1 := string(bpfProgEth1.UID) + + expectedLoadReq1 := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Tc.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(uuid1), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_TcxAttachInfo{ + TcxAttachInfo: &gobpfman.TCXAttachInfo{ + Iface: fakeInts[1], + Priority: tcx.Spec.Priority, + Direction: direction, + Netns: &netns, + }, + }, + }, + } + + // Check that the BpfNsProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + // prog ID should already have been set + id0, err := GetID(bpfProgEth0) + require.NoError(t, err) + + // Check that the BpfNsProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + // prog ID should already have been set + id1, err := GetID(bpfProgEth1) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly built + if !cmp.Equal(expectedLoadReq0, cli.LoadRequests[int(*id0)], protocmp.Transform()) { + t.Logf("Diff %v", cmp.Diff(expectedLoadReq0, cli.LoadRequests[int(*id0)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Check the bpfLoadRequest was correctly built + if !cmp.Equal(expectedLoadReq1, cli.LoadRequests[int(*id1)], protocmp.Transform()) { + t.Logf("Diff %v", cmp.Diff(expectedLoadReq1, cli.LoadRequests[int(*id1)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Check that the BpfNsProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + // Check that the BpfNsProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + // Third reconcile should update the BpfNsPrograms status to loaded + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check that the BpfNsProgram's status was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth0.Status.Conditions[0].Type) + + // Check that the BpfNsProgram's status was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth1.Status.Conditions[0].Type) +} diff --git a/controllers/bpfman-agent/tcx-program.go b/controllers/bpfman-agent/tcx-program.go index 8df8661f2..0a6c4ed06 100644 --- a/controllers/bpfman-agent/tcx-program.go +++ b/controllers/bpfman-agent/tcx-program.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanagent import ( @@ -41,7 +43,7 @@ import ( // TcxProgramReconciler reconciles a tcxProgram object by creating multiple // bpfProgram objects and managing bpfman for each one. type TcxProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler currentTcxProgram *bpfmaniov1alpha1.TcxProgram interfaces []string ourNode *v1.Node @@ -71,6 +73,14 @@ func (r *TcxProgramReconciler) getName() string { return r.currentTcxProgram.Name } +func (r *TcxProgramReconciler) getNamespace() string { + return r.currentTcxProgram.Namespace +} + +func (r *TcxProgramReconciler) getNoContAnnotationIndex() string { + return internal.TcxNoContainersOnNode +} + func (r *TcxProgramReconciler) getNode() *v1.Node { return r.ourNode } @@ -149,7 +159,13 @@ func (r *TcxProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpf // There is a container selector, so see if there are any matching // containers on this node. - containerInfo, err := r.Containers.GetContainers(ctx, r.currentTcxProgram.Spec.Containers, r.Logger) + containerInfo, err := r.Containers.GetContainers( + ctx, + r.currentTcxProgram.Spec.Containers.Namespace, + r.currentTcxProgram.Spec.Containers.Pods, + r.currentTcxProgram.Spec.Containers.ContainerNames, + r.Logger, + ) if err != nil { return nil, fmt.Errorf("failed to get container pids: %v", err) } diff --git a/controllers/bpfman-agent/tcx-program_test.go b/controllers/bpfman-agent/tcx-program_test.go index 090d640de..7b343ff4d 100644 --- a/controllers/bpfman-agent/tcx-program_test.go +++ b/controllers/bpfman-agent/tcx-program_test.go @@ -24,7 +24,6 @@ import ( "google.golang.org/protobuf/testing/protocmp" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" testutils "github.com/bpfman/bpfman-operator/internal/test-utils" @@ -98,18 +97,21 @@ func TestTcxProgramControllerCreate(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &TcxProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &TcxProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -127,7 +129,7 @@ func TestTcxProgramControllerCreate(t *testing.T) { } // Check the BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.NotEmpty(t, bpfProg) @@ -178,11 +180,11 @@ func TestTcxProgramControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) // prog ID should already have been set - id, err := bpfmanagentinternal.GetID(bpfProg) + id, err := GetID(bpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly built @@ -201,7 +203,7 @@ func TestTcxProgramControllerCreate(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) @@ -265,18 +267,21 @@ func TestTcxProgramControllerCreateMultiIntf(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &TcxProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &TcxProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -294,7 +299,7 @@ func TestTcxProgramControllerCreateMultiIntf(t *testing.T) { } // Check the first BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) require.NotEmpty(t, bpfProgEth0) @@ -342,7 +347,7 @@ func TestTcxProgramControllerCreateMultiIntf(t *testing.T) { require.False(t, res.Requeue) // Check the Second BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) require.NotEmpty(t, bpfProgEth1) @@ -418,19 +423,19 @@ func TestTcxProgramControllerCreateMultiIntf(t *testing.T) { } // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) // prog ID should already have been set - id0, err := bpfmanagentinternal.GetID(bpfProgEth0) + id0, err := GetID(bpfProgEth0) require.NoError(t, err) // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) // prog ID should already have been set - id1, err := bpfmanagentinternal.GetID(bpfProgEth1) + id1, err := GetID(bpfProgEth1) require.NoError(t, err) // Check the bpfLoadRequest was correctly built @@ -446,11 +451,11 @@ func TestTcxProgramControllerCreateMultiIntf(t *testing.T) { } // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) // Third reconcile should update the bpfPrograms status to loaded @@ -463,13 +468,13 @@ func TestTcxProgramControllerCreateMultiIntf(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth0.Status.Conditions[0].Type) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth1.Status.Conditions[0].Type) diff --git a/controllers/bpfman-agent/test_common.go b/controllers/bpfman-agent/test_common.go index b6e795036..a0b9f48b0 100644 --- a/controllers/bpfman-agent/test_common.go +++ b/controllers/bpfman-agent/test_common.go @@ -17,8 +17,13 @@ type FakeContainerGetter struct { containerList *[]ContainerInfo } -func (f *FakeContainerGetter) GetContainers(ctx context.Context, containerSelector *bpfmaniov1alpha1.ContainerSelector, - logger logr.Logger) (*[]ContainerInfo, error) { +func (f *FakeContainerGetter) GetContainers( + ctx context.Context, + selectorNamespace string, + selectorPods metav1.LabelSelector, + selectorContainerNames *[]string, + logger logr.Logger, +) (*[]ContainerInfo, error) { return f.containerList, nil } @@ -64,7 +69,11 @@ func TestGetPods(t *testing.T) { require.NoError(t, err) // Call getPods and check the returned PodList - podList, err := containerGetter.getPodsForNode(ctx, containerSelector) + podList, err := containerGetter.getPodsForNode( + ctx, + containerSelector.Namespace, + containerSelector.Pods, + ) require.NoError(t, err) require.Len(t, podList.Items, 1) require.Equal(t, "test-pod", podList.Items[0].Name) @@ -77,7 +86,11 @@ func TestGetPods(t *testing.T) { }, } - podList, err = containerGetter.getPodsForNode(ctx, containerSelector) + podList, err = containerGetter.getPodsForNode( + ctx, + containerSelector.Namespace, + containerSelector.Pods, + ) require.NoError(t, err) require.Len(t, podList.Items, 1) require.Equal(t, "test-pod", podList.Items[0].Name) diff --git a/controllers/bpfman-agent/tracepoint-program.go b/controllers/bpfman-agent/tracepoint-program.go index 97480369c..7855399fd 100644 --- a/controllers/bpfman-agent/tracepoint-program.go +++ b/controllers/bpfman-agent/tracepoint-program.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanagent import ( @@ -39,7 +41,7 @@ import ( // BpfProgramReconciler reconciles a BpfProgram object type TracepointProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler ourNode *v1.Node currentTracepointProgram *bpfmaniov1alpha1.TracepointProgram } @@ -68,6 +70,14 @@ func (r *TracepointProgramReconciler) getName() string { return r.currentTracepointProgram.Name } +func (r *TracepointProgramReconciler) getNamespace() string { + return r.currentTracepointProgram.Namespace +} + +func (r *TracepointProgramReconciler) getNoContAnnotationIndex() string { + return internal.TracepointNoContainersOnNode +} + func (r *TracepointProgramReconciler) getNode() *v1.Node { return r.ourNode } diff --git a/controllers/bpfman-agent/tracepoint-program_test.go b/controllers/bpfman-agent/tracepoint-program_test.go index 3455f72a4..876fb5bf6 100644 --- a/controllers/bpfman-agent/tracepoint-program_test.go +++ b/controllers/bpfman-agent/tracepoint-program_test.go @@ -24,7 +24,6 @@ import ( "google.golang.org/protobuf/testing/protocmp" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" internal "github.com/bpfman/bpfman-operator/internal" testutils "github.com/bpfman/bpfman-operator/internal/test-utils" @@ -92,18 +91,21 @@ func TestTracepointProgramControllerCreate(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &TracepointProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &TracepointProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -121,7 +123,7 @@ func TestTracepointProgramControllerCreate(t *testing.T) { } // Check the BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.NotEmpty(t, bpfProg) @@ -170,11 +172,11 @@ func TestTracepointProgramControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) // prog ID should already have been set - id, err := bpfmanagentinternal.GetID(bpfProg) + id, err := GetID(bpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly Built @@ -184,7 +186,7 @@ func TestTracepointProgramControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) // Third reconcile should update the bpfPrograms status to loaded @@ -197,7 +199,7 @@ func TestTracepointProgramControllerCreate(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) diff --git a/controllers/bpfman-agent/uprobe-ns-program.go b/controllers/bpfman-agent/uprobe-ns-program.go new file mode 100644 index 000000000..a6ab77581 --- /dev/null +++ b/controllers/bpfman-agent/uprobe-ns-program.go @@ -0,0 +1,296 @@ +/* +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. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanagent + +import ( + "context" + "fmt" + "strconv" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" + internal "github.com/bpfman/bpfman-operator/internal" + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=uprobensprograms,verbs=get;list;watch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=uprobensprograms,verbs=get;list;watch + +// BpfProgramReconciler reconciles a BpfNsProgram object +type UprobeNsProgramReconciler struct { + NamespaceProgramReconciler + currentUprobeNsProgram *bpfmaniov1alpha1.UprobeNsProgram + ourNode *v1.Node +} + +func (r *UprobeNsProgramReconciler) getFinalizer() string { + return r.finalizer +} + +func (r *UprobeNsProgramReconciler) getOwner() metav1.Object { + if r.appOwner == nil { + return r.currentUprobeNsProgram + } else { + return r.appOwner + } +} + +func (r *UprobeNsProgramReconciler) getRecType() string { + return r.recType +} + +func (r *UprobeNsProgramReconciler) getProgType() internal.ProgramType { + return internal.Kprobe +} + +func (r *UprobeNsProgramReconciler) getName() string { + return r.currentUprobeNsProgram.Name +} + +func (r *UprobeNsProgramReconciler) getNamespace() string { + return r.currentUprobeNsProgram.Namespace +} + +func (r *UprobeNsProgramReconciler) getNoContAnnotationIndex() string { + return internal.UprobeNsNoContainersOnNode +} + +func (r *UprobeNsProgramReconciler) getNode() *v1.Node { + return r.ourNode +} + +func (r *UprobeNsProgramReconciler) getBpfProgramCommon() *bpfmaniov1alpha1.BpfProgramCommon { + return &r.currentUprobeNsProgram.Spec.BpfProgramCommon +} + +func (r *UprobeNsProgramReconciler) getNodeSelector() *metav1.LabelSelector { + return &r.currentUprobeNsProgram.Spec.NodeSelector +} + +func (r *UprobeNsProgramReconciler) getBpfGlobalData() map[string][]byte { + return r.currentUprobeNsProgram.Spec.GlobalData +} + +func (r *UprobeNsProgramReconciler) getAppProgramId() string { + return appProgramId(r.currentUprobeNsProgram.GetLabels()) +} + +func (r *UprobeNsProgramReconciler) setCurrentProgram(program client.Object) error { + var ok bool + + r.currentUprobeNsProgram, ok = program.(*bpfmaniov1alpha1.UprobeNsProgram) + if !ok { + return fmt.Errorf("failed to cast program to UprobeNsProgram") + } + + return nil +} + +// SetupWithManager sets up the controller with the Manager. +// The Bpfman-Agent should reconcile whenever a UprobeNsProgram is updated, +// load the program to the node via bpfman, and then create a BpfNsProgram object +// to reflect per node state information. +func (r *UprobeNsProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.UprobeNsProgram{}, builder.WithPredicates(predicate.And(predicate.GenerationChangedPredicate{}, predicate.ResourceVersionChangedPredicate{}))). + Owns(&bpfmaniov1alpha1.BpfNsProgram{}, + builder.WithPredicates(predicate.And( + internal.BpfNsProgramTypePredicate(internal.UprobeString), + internal.BpfProgramNodePredicate(r.NodeName)), + ), + ). + // Trigger reconciliation if node labels change since that could make + // the UprobeNsProgram no longer select the Node. Trigger on pod events + // for when uprobes are attached inside containers. In both cases, only + // care about events specific to our node + Watches( + &v1.Node{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And(predicate.LabelChangedPredicate{}, nodePredicate(r.NodeName))), + ). + // Watch for changes in Pod resources in case we are using a container selector. + Watches( + &v1.Pod{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(podOnNodePredicate(r.NodeName)), + ). + Complete(r) +} + +func (r *UprobeNsProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfNsProgramList, error) { + progs := &bpfmaniov1alpha1.BpfNsProgramList{} + + sanitizedUprobe := sanitize(r.currentUprobeNsProgram.Spec.Target) + "-" + sanitize(r.currentUprobeNsProgram.Spec.FunctionName) + + // There is a container selector, so see if there are any matching + // containers on this node. + containerInfo, err := r.Containers.GetContainers( + ctx, + r.getNamespace(), + r.currentUprobeNsProgram.Spec.Containers.Pods, + r.currentUprobeNsProgram.Spec.Containers.ContainerNames, + r.Logger, + ) + if err != nil { + return nil, fmt.Errorf("failed to get container pids: %v", err) + } + if containerInfo == nil || len(*containerInfo) == 0 { + // There were no errors, but the container selector didn't + // select any containers on this node. + + annotations := map[string]string{ + internal.UprobeNsProgramTarget: r.currentUprobeNsProgram.Spec.Target, + internal.UprobeNsNoContainersOnNode: "true", + } + + attachPoint := sanitizedUprobe + "-no-containers-on-node" + + prog, err := r.createBpfProgram(attachPoint, r, annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfNsProgram %s: %v", attachPoint, err) + } + + progs.Items = append(progs.Items, *prog) + } else { + + // Containers were found, so create BpfNsPrograms. + for i := range *containerInfo { + container := (*containerInfo)[i] + + annotations := map[string]string{internal.UprobeNsProgramTarget: r.currentUprobeNsProgram.Spec.Target} + annotations[internal.UprobeNsContainerPid] = strconv.FormatInt(container.pid, 10) + + attachPoint := fmt.Sprintf("%s-%s-%s", + sanitizedUprobe, + container.podName, + container.containerName, + ) + + prog, err := r.createBpfProgram(attachPoint, r, annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfNsProgram %s: %v", attachPoint, err) + } + + progs.Items = append(progs.Items, *prog) + } + } + + return progs, nil +} + +func (r *UprobeNsProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // Initialize node and current program + r.currentUprobeNsProgram = &bpfmaniov1alpha1.UprobeNsProgram{} + r.finalizer = internal.UprobeNsProgramControllerFinalizer + r.recType = internal.UprobeString + r.ourNode = &v1.Node{} + r.Logger = ctrl.Log.WithName("uprobe-ns") + + r.Logger.Info("bpfman-agent enter: uprobe-ns", "Namespace", req.Namespace, "Name", req.Name) + + // Lookup K8s node object for this bpfman-agent This should always succeed + if err := r.Get(ctx, types.NamespacedName{Namespace: v1.NamespaceAll, Name: r.NodeName}, r.ourNode); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfman-agent node %s : %v", + req.NamespacedName, err) + } + + uprobePrograms := &bpfmaniov1alpha1.UprobeNsProgramList{} + + opts := []client.ListOption{} + + if err := r.List(ctx, uprobePrograms, opts...); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting UprobeNsPrograms for full reconcile %s : %v", + req.NamespacedName, err) + } + + if len(uprobePrograms.Items) == 0 { + r.Logger.Info("UprobeNsProgramController found no Uprobe Programs") + return ctrl.Result{Requeue: false}, nil + } + + // Create a list of Uprobe programs to pass into reconcileCommon() + var uprobeObjects []client.Object = make([]client.Object, len(uprobePrograms.Items)) + for i := range uprobePrograms.Items { + uprobeObjects[i] = &uprobePrograms.Items[i] + } + + // Reconcile each TcProgram. + _, result, err := r.reconcileCommon(ctx, r, uprobeObjects) + return result, err +} + +func (r *UprobeNsProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfNsProgram, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) { + bytecode, err := bpfmanagentinternal.GetBytecode(r.Client, &r.currentUprobeNsProgram.Spec.ByteCode) + if err != nil { + return nil, fmt.Errorf("failed to process bytecode selector: %v", err) + } + + var uprobeAttachInfo *gobpfman.UprobeAttachInfo + + var containerPid int32 + hasContainerPid := false + + containerPidStr, ok := bpfProgram.Annotations[internal.UprobeNsContainerPid] + + if ok { + containerPidInt64, err := strconv.ParseInt(containerPidStr, 10, 32) + if err != nil { + r.Logger.Error(err, "ParseInt() error on containerPidStr", "containerPidStr", containerPidStr) + } else { + containerPid = int32(containerPidInt64) + hasContainerPid = true + } + } + + uprobeAttachInfo = &gobpfman.UprobeAttachInfo{ + FnName: &r.currentUprobeNsProgram.Spec.FunctionName, + Offset: r.currentUprobeNsProgram.Spec.Offset, + Target: bpfProgram.Annotations[internal.UprobeNsProgramTarget], + Retprobe: r.currentUprobeNsProgram.Spec.RetProbe, + } + + if hasContainerPid { + uprobeAttachInfo.ContainerPid = &containerPid + } + + loadRequest := gobpfman.LoadRequest{ + Bytecode: bytecode, + Name: r.currentUprobeNsProgram.Spec.BpfFunctionName, + ProgramType: uint32(internal.Kprobe), + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_UprobeAttachInfo{ + UprobeAttachInfo: uprobeAttachInfo, + }, + }, + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.getOwner().GetName()}, + GlobalData: r.currentUprobeNsProgram.Spec.GlobalData, + MapOwnerId: mapOwnerId, + } + + return &loadRequest, nil +} diff --git a/controllers/bpfman-agent/uprobe-ns-program_test.go b/controllers/bpfman-agent/uprobe-ns-program_test.go new file mode 100644 index 000000000..4af32f64b --- /dev/null +++ b/controllers/bpfman-agent/uprobe-ns-program_test.go @@ -0,0 +1,243 @@ +/* +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 bpfmanagent + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" + internal "github.com/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestUprobeNsProgramControllerCreate(t *testing.T) { + var ( + name = "fakeUprobeProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + functionName = "malloc" + target = "libc" + offset = 0 + retprobe = false + fakeNode = testutils.NewNode("fake-control-plane") + fakePodName = "my-pod" + fakeContainerName = "my-container-1" + fakePid = int64(8712) + ctx = context.TODO() + appProgramId = "" + bpfProg = &bpfmaniov1alpha1.BpfNsProgram{} + fakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81" + attachPoint = fmt.Sprintf("%s-%s-%s-%s", + sanitize(target), + sanitize(functionName), + fakePodName, + fakeContainerName, + ) + ) + // A UprobeProgram object with metadata and spec. + Uprobe := &bpfmaniov1alpha1.UprobeNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: bpfmaniov1alpha1.UprobeNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + UprobeNsProgramInfo: bpfmaniov1alpha1.UprobeNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + FunctionName: functionName, + Target: target, + Offset: uint64(offset), + RetProbe: retprobe, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": fakePodName, + }, + }, + }, + }, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, Uprobe} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, Uprobe) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.UprobeNsProgramList{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(Uprobe).WithStatusSubresource(&bpfmaniov1alpha1.BpfNsProgram{}).WithRuntimeObjects(objs...).Build() + + cli := agenttestutils.NewBpfmanClientFake() + + testContainers := FakeContainerGetter{ + containerList: &[]ContainerInfo{ + { + podName: fakePodName, + containerName: fakeContainerName, + pid: fakePid, + }, + }, + } + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + BpfmanClient: cli, + NodeName: fakeNode.Name, + Containers: &testContainers, + } + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &UprobeNsProgramReconciler{NamespaceProgramReconciler: npr, ourNode: fakeNode} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should create the bpf program object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Check the BpfProgram Object was created successfully + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) + require.NoError(t, err) + + require.NotEmpty(t, bpfProg) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProg.Finalizers[0]) + // owningConfig Label was correctly set + require.Equal(t, bpfProg.Labels[internal.BpfProgramOwner], name) + // node Label was correctly set + require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name) + // uprobe function Annotation was correctly set + require.Equal(t, bpfProg.Annotations[internal.UprobeNsProgramTarget], target) + // Type is set + require.Equal(t, r.getRecType(), bpfProg.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of bpfProgram with Fake UID since the fake API server won't + bpfProg.UID = types.UID(fakeUID) + err = cl.Update(ctx, bpfProg) + require.NoError(t, err) + + // Second reconcile should create the bpfman Load Request and update the + // BpfProgram object's maps field and id annotation. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + pid32 := int32(fakePid) + + // Require no requeue + require.False(t, res.Requeue) + expectedLoadReq := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Kprobe.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProg.UID), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_UprobeAttachInfo{ + UprobeAttachInfo: &gobpfman.UprobeAttachInfo{ + FnName: &functionName, + Target: target, + Offset: uint64(offset), + Retprobe: retprobe, + ContainerPid: &pid32, + }, + }, + }, + } + + // Check that the bpfProgram's programs was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) + require.NoError(t, err) + + // prog ID should already have been set + id, err := GetID(bpfProg) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly Built + if !cmp.Equal(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) { + cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) + t.Logf("Diff %v", cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Third reconcile should set the status to loaded + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check that the bpfProgram's status was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) +} diff --git a/controllers/bpfman-agent/uprobe-program.go b/controllers/bpfman-agent/uprobe-program.go index 72f90ed31..20875729f 100644 --- a/controllers/bpfman-agent/uprobe-program.go +++ b/controllers/bpfman-agent/uprobe-program.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanagent import ( @@ -40,7 +42,7 @@ import ( // BpfProgramReconciler reconciles a BpfProgram object type UprobeProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler currentUprobeProgram *bpfmaniov1alpha1.UprobeProgram ourNode *v1.Node } @@ -69,6 +71,14 @@ func (r *UprobeProgramReconciler) getName() string { return r.currentUprobeProgram.Name } +func (r *UprobeProgramReconciler) getNamespace() string { + return r.currentUprobeProgram.Namespace +} + +func (r *UprobeProgramReconciler) getNoContAnnotationIndex() string { + return internal.UprobeNoContainersOnNode +} + func (r *UprobeProgramReconciler) getNode() *v1.Node { return r.ourNode } @@ -140,7 +150,13 @@ func (r *UprobeProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (* // There is a container selector, so see if there are any matching // containers on this node. - containerInfo, err := r.Containers.GetContainers(ctx, r.currentUprobeProgram.Spec.Containers, r.Logger) + containerInfo, err := r.Containers.GetContainers( + ctx, + r.currentUprobeProgram.Spec.Containers.Namespace, + r.currentUprobeProgram.Spec.Containers.Pods, + r.currentUprobeProgram.Spec.Containers.ContainerNames, + r.Logger, + ) if err != nil { return nil, fmt.Errorf("failed to get container pids: %v", err) } diff --git a/controllers/bpfman-agent/uprobe-program_test.go b/controllers/bpfman-agent/uprobe-program_test.go index a22d367d2..36abeaa15 100644 --- a/controllers/bpfman-agent/uprobe-program_test.go +++ b/controllers/bpfman-agent/uprobe-program_test.go @@ -25,7 +25,6 @@ import ( "google.golang.org/protobuf/testing/protocmp" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" internal "github.com/bpfman/bpfman-operator/internal" testutils "github.com/bpfman/bpfman-operator/internal/test-utils" @@ -99,18 +98,21 @@ func TestUprobeProgramControllerCreate(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &UprobeProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &UprobeProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -128,7 +130,7 @@ func TestUprobeProgramControllerCreate(t *testing.T) { } // Check the BpfProgram Object was created successfully - err = r.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.NotEmpty(t, bpfProg) @@ -180,11 +182,11 @@ func TestUprobeProgramControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = r.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) // prog ID should already have been set - id, err := bpfmanagentinternal.GetID(bpfProg) + id, err := GetID(bpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly Built @@ -204,7 +206,7 @@ func TestUprobeProgramControllerCreate(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = r.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) @@ -290,7 +292,7 @@ func TestUprobeProgramControllerCreateContainer(t *testing.T) { }, } - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, @@ -298,11 +300,15 @@ func TestUprobeProgramControllerCreateContainer(t *testing.T) { Containers: &testContainers, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &UprobeProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &UprobeProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -320,7 +326,7 @@ func TestUprobeProgramControllerCreateContainer(t *testing.T) { } // Check the BpfProgram Object was created successfully - err = r.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.NotEmpty(t, bpfProg) @@ -375,11 +381,11 @@ func TestUprobeProgramControllerCreateContainer(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = r.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) // prog ID should already have been set - id, err := bpfmanagentinternal.GetID(bpfProg) + id, err := GetID(bpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly Built @@ -399,7 +405,7 @@ func TestUprobeProgramControllerCreateContainer(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = r.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) diff --git a/controllers/bpfman-agent/xdp-ns-program.go b/controllers/bpfman-agent/xdp-ns-program.go new file mode 100644 index 000000000..e87551909 --- /dev/null +++ b/controllers/bpfman-agent/xdp-ns-program.go @@ -0,0 +1,295 @@ +/* +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. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanagent + +import ( + "context" + "fmt" + "strconv" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" + internal "github.com/bpfman/bpfman-operator/internal" + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=xdpnsprograms,verbs=get;list;watch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=xdpnsprograms,verbs=get;list;watch + +// BpfProgramReconciler reconciles a BpfNsProgram object +type XdpNsProgramReconciler struct { + NamespaceProgramReconciler + currentXdpNsProgram *bpfmaniov1alpha1.XdpNsProgram + interfaces []string + ourNode *v1.Node +} + +func (r *XdpNsProgramReconciler) getFinalizer() string { + return r.finalizer +} + +func (r *XdpNsProgramReconciler) getOwner() metav1.Object { + if r.appOwner == nil { + return r.currentXdpNsProgram + } else { + return r.appOwner + } +} + +func (r *XdpNsProgramReconciler) getRecType() string { + return r.recType +} + +func (r *XdpNsProgramReconciler) getProgType() internal.ProgramType { + return internal.Xdp +} + +func (r *XdpNsProgramReconciler) getName() string { + return r.currentXdpNsProgram.Name +} + +func (r *XdpNsProgramReconciler) getNamespace() string { + return r.currentXdpNsProgram.Namespace +} + +func (r *XdpNsProgramReconciler) getNoContAnnotationIndex() string { + return internal.XdpNsNoContainersOnNode +} + +func (r *XdpNsProgramReconciler) getNode() *v1.Node { + return r.ourNode +} + +func (r *XdpNsProgramReconciler) getBpfProgramCommon() *bpfmaniov1alpha1.BpfProgramCommon { + return &r.currentXdpNsProgram.Spec.BpfProgramCommon +} + +func (r *XdpNsProgramReconciler) getNodeSelector() *metav1.LabelSelector { + return &r.currentXdpNsProgram.Spec.NodeSelector +} + +func (r *XdpNsProgramReconciler) getBpfGlobalData() map[string][]byte { + return r.currentXdpNsProgram.Spec.GlobalData +} + +func (r *XdpNsProgramReconciler) getAppProgramId() string { + return appProgramId(r.currentXdpNsProgram.GetLabels()) +} + +func (r *XdpNsProgramReconciler) setCurrentProgram(program client.Object) error { + var err error + var ok bool + + r.currentXdpNsProgram, ok = program.(*bpfmaniov1alpha1.XdpNsProgram) + if !ok { + return fmt.Errorf("failed to cast program to XdpNsProgram") + } + + r.interfaces, err = getInterfaces(&r.currentXdpNsProgram.Spec.InterfaceSelector, r.ourNode) + if err != nil { + return fmt.Errorf("failed to get interfaces for XdpNsProgram: %v", err) + } + + return nil +} + +// SetupWithManager sets up the controller with the Manager. +// The Bpfman-Agent should reconcile whenever a XdpNsProgram is updated, +// load the program to the node via bpfman, and then create a bpfProgram object +// to reflect per node state information. +func (r *XdpNsProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.XdpNsProgram{}, + builder.WithPredicates(predicate.And( + predicate.GenerationChangedPredicate{}, + predicate.ResourceVersionChangedPredicate{}), + ), + ). + Owns(&bpfmaniov1alpha1.BpfNsProgram{}, + builder.WithPredicates(predicate.And( + internal.BpfNsProgramTypePredicate(internal.Xdp.String()), + internal.BpfProgramNodePredicate(r.NodeName)), + ), + ). + // Only trigger reconciliation if node labels change since that could + // make the XdpNsProgram no longer select the Node. Additionally only + // care about node events specific to our node + Watches( + &v1.Node{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And(predicate.LabelChangedPredicate{}, nodePredicate(r.NodeName))), + ). + // Watch for changes in Pod resources in case we are using a container selector. + Watches( + &v1.Pod{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(podOnNodePredicate(r.NodeName)), + ). + Complete(r) +} + +func (r *XdpNsProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfNsProgramList, error) { + progs := &bpfmaniov1alpha1.BpfNsProgramList{} + + // There is a container selector, so see if there are any matching + // containers on this node. + containerInfo, err := r.Containers.GetContainers( + ctx, + r.getNamespace(), + r.currentXdpNsProgram.Spec.Containers.Pods, + r.currentXdpNsProgram.Spec.Containers.ContainerNames, + r.Logger, + ) + if err != nil { + return nil, fmt.Errorf("failed to get container pids: %v", err) + } + + if containerInfo == nil || len(*containerInfo) == 0 { + // There were no errors, but the container selector didn't + // select any containers on this node. + for _, iface := range r.interfaces { + attachPoint := fmt.Sprintf("%s-%s", + iface, + "no-containers-on-node", + ) + + annotations := map[string]string{ + internal.XdpNsProgramInterface: iface, + internal.XdpNsNoContainersOnNode: "true", + } + + prog, err := r.createBpfProgram(attachPoint, r, annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfProgram %s: %v", attachPoint, err) + } + + progs.Items = append(progs.Items, *prog) + } + } else { + // Containers were found, so create bpfPrograms. + for i := range *containerInfo { + container := (*containerInfo)[i] + for _, iface := range r.interfaces { + attachPoint := fmt.Sprintf("%s-%s-%s", + iface, + container.podName, + container.containerName, + ) + + annotations := map[string]string{ + internal.XdpNsProgramInterface: iface, + internal.XdpNsContainerPid: strconv.FormatInt(container.pid, 10), + } + + prog, err := r.createBpfProgram(attachPoint, r, annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfProgram %s: %v", attachPoint, err) + } + + progs.Items = append(progs.Items, *prog) + } + } + } + + return progs, nil +} + +func (r *XdpNsProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // Initialize node and current program + r.currentXdpNsProgram = &bpfmaniov1alpha1.XdpNsProgram{} + r.finalizer = internal.XdpNsProgramControllerFinalizer + r.recType = internal.Xdp.String() + r.ourNode = &v1.Node{} + r.Logger = ctrl.Log.WithName("xdp-ns") + + r.Logger.Info("bpfman-agent enter: xdp-ns", "Namespace", req.Namespace, "Name", req.Name) + + // Lookup K8s node object for this bpfman-agent This should always succeed + if err := r.Get(ctx, types.NamespacedName{Namespace: v1.NamespaceAll, Name: r.NodeName}, r.ourNode); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfman-agent node %s : %v", + req.NamespacedName, err) + } + + xdpPrograms := &bpfmaniov1alpha1.XdpNsProgramList{} + opts := []client.ListOption{} + + if err := r.List(ctx, xdpPrograms, opts...); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting XdpNsPrograms for full reconcile %s : %v", + req.NamespacedName, err) + } + + if len(xdpPrograms.Items) == 0 { + r.Logger.Info("XdpNsProgramController found no XDP Programs") + return ctrl.Result{Requeue: false}, nil + } + + // Create a list of Xdp programs to pass into reconcileCommon() + var xdpObjects []client.Object = make([]client.Object, len(xdpPrograms.Items)) + for i := range xdpPrograms.Items { + xdpObjects[i] = &xdpPrograms.Items[i] + } + + // Reconcile each TcProgram. + _, result, err := r.reconcileCommon(ctx, r, xdpObjects) + return result, err +} + +func (r *XdpNsProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfNsProgram, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) { + bytecode, err := bpfmanagentinternal.GetBytecode(r.Client, &r.currentXdpNsProgram.Spec.ByteCode) + if err != nil { + return nil, fmt.Errorf("failed to process bytecode selector: %v", err) + } + + attachInfo := &gobpfman.XDPAttachInfo{ + Priority: r.currentXdpNsProgram.Spec.Priority, + Iface: bpfProgram.Annotations[internal.XdpNsProgramInterface], + ProceedOn: xdpProceedOnToInt(r.currentXdpNsProgram.Spec.ProceedOn), + } + + containerPidStr, ok := bpfProgram.Annotations[internal.XdpNsContainerPid] + if ok { + netns := fmt.Sprintf("/host/proc/%s/ns/net", containerPidStr) + attachInfo.Netns = &netns + } + + loadRequest := gobpfman.LoadRequest{ + Bytecode: bytecode, + Name: r.currentXdpNsProgram.Spec.BpfFunctionName, + ProgramType: uint32(internal.Xdp), + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_XdpAttachInfo{ + XdpAttachInfo: attachInfo, + }, + }, + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.getOwner().GetName()}, + GlobalData: r.currentXdpNsProgram.Spec.GlobalData, + MapOwnerId: mapOwnerId, + } + + return &loadRequest, nil +} diff --git a/controllers/bpfman-agent/xdp-ns-program_test.go b/controllers/bpfman-agent/xdp-ns-program_test.go new file mode 100644 index 000000000..316f86318 --- /dev/null +++ b/controllers/bpfman-agent/xdp-ns-program_test.go @@ -0,0 +1,390 @@ +/* +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 bpfmanagent + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" + internal "github.com/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +// Runs the XdpProgramControllerCreate test. If multiInterface = true, it +// installs the program on two interfaces. If multiCondition == true, it runs +// it with an error case in which the program object has multiple conditions. +func xdpNsProgramControllerCreate(t *testing.T, multiInterface bool, multiCondition bool) { + var ( + name = "fakeXdpProgram" + namespace = "my-namespace" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + fakeNode = testutils.NewNode("fake-control-plane") + fakePodName = "my-pod" + fakeContainerName = "my-container-1" + fakePid = int64(12201) + fakeInt0 = "eth0" + fakeInt1 = "eth1" + ctx = context.TODO() + appProgramId0 = "" + appProgramId1 = "" + bpfProgEth0 = &bpfmaniov1alpha1.BpfNsProgram{} + bpfProgEth1 = &bpfmaniov1alpha1.BpfNsProgram{} + fakeUID0 = "ef71d42c-aa21-48e8-a697-82391d801a80" + fakeUID1 = "ef71d42c-aa21-48e8-a697-82391d801a81" + attachPoint0 = fmt.Sprintf("%s-%s-%s", + fakeInt0, + fakePodName, + fakeContainerName, + ) + attachPoint1 = fmt.Sprintf("%s-%s-%s", + fakeInt1, + fakePodName, + fakeContainerName, + ) + ) + + var fakeInts []string + if multiInterface { + fakeInts = []string{fakeInt0, fakeInt1} + } else { + fakeInts = []string{fakeInt0} + } + + // A XdpNsProgram object with metadata and spec. + xdp := &bpfmaniov1alpha1.XdpNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: bpfmaniov1alpha1.XdpNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + XdpNsProgramInfo: bpfmaniov1alpha1.XdpNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &fakeInts, + }, + Priority: 0, + ProceedOn: []bpfmaniov1alpha1.XdpProceedOnValue{bpfmaniov1alpha1.XdpProceedOnValue("pass"), + bpfmaniov1alpha1.XdpProceedOnValue("dispatcher_return"), + }, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": fakePodName, + }, + }, + }, + }, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, xdp} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, xdp) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.XdpNsProgramList{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(xdp).WithStatusSubresource(&bpfmaniov1alpha1.BpfNsProgram{}).WithRuntimeObjects(objs...).Build() + + cli := agenttestutils.NewBpfmanClientFake() + + testContainers := FakeContainerGetter{ + containerList: &[]ContainerInfo{ + { + podName: fakePodName, + containerName: fakeContainerName, + pid: fakePid, + }, + }, + } + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + BpfmanClient: cli, + NodeName: fakeNode.Name, + Containers: &testContainers, + } + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &XdpNsProgramReconciler{NamespaceProgramReconciler: npr, ourNode: fakeNode} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should create the first bpf program object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Check the first BpfProgram Object was created successfully + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + require.NotEmpty(t, bpfProgEth0) + // owningConfig Label was correctly set + require.Equal(t, bpfProgEth0.Labels[internal.BpfProgramOwner], name) + // node Label was correctly set + require.Equal(t, bpfProgEth0.Labels[internal.K8sHostLabel], fakeNode.Name) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProgEth0.Finalizers[0]) + // Type is set + require.Equal(t, r.getRecType(), bpfProgEth0.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of bpfProgram with Fake UID since the fake API server won't + bpfProgEth0.UID = types.UID(fakeUID0) + err = cl.Update(ctx, bpfProgEth0) + require.NoError(t, err) + + // Second reconcile should create the bpfman Load Requests for the first bpfProgram. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + uuid0 := string(bpfProgEth0.UID) + netns := fmt.Sprintf("/host/proc/%d/ns/net", fakePid) + + expectedLoadReq0 := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Xdp.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: uuid0, internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_XdpAttachInfo{ + XdpAttachInfo: &gobpfman.XDPAttachInfo{ + Priority: 0, + Iface: fakeInts[0], + ProceedOn: []int32{2, 31}, + Netns: &netns, + }, + }, + }, + } + + // Check that the bpfProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + // prog ID should already have been set + id0, err := GetID(bpfProgEth0) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly built + if !cmp.Equal(expectedLoadReq0, cli.LoadRequests[int(*id0)], protocmp.Transform()) { + t.Logf("Diff %v", cmp.Diff(expectedLoadReq0, cli.LoadRequests[int(*id0)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // NOTE: THIS IS A TEST FOR AN ERROR PATH. THERE SHOULD NEVER BE MORE THAN + // ONE CONDITION. + if multiCondition { + // Add some random conditions and verify that the condition still gets + // updated correctly. + meta.SetStatusCondition(&bpfProgEth0.Status.Conditions, bpfmaniov1alpha1.BpfProgCondBytecodeSelectorError.Condition()) + if err := r.Status().Update(ctx, bpfProgEth0); err != nil { + r.Logger.V(1).Info("failed to set KprobeProgram object status") + } + meta.SetStatusCondition(&bpfProgEth0.Status.Conditions, bpfmaniov1alpha1.BpfProgCondNotSelected.Condition()) + if err := r.Status().Update(ctx, bpfProgEth0); err != nil { + r.Logger.V(1).Info("failed to set KprobeProgram object status") + } + // Make sure we have 2 conditions + require.Equal(t, 2, len(bpfProgEth0.Status.Conditions)) + } + + // Third reconcile should update the bpfPrograms status to loaded + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Get program object + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + // Check that the bpfProgram's status was correctly updated + // Make sure we only have 1 condition now + require.Equal(t, 1, len(bpfProgEth0.Status.Conditions)) + // Make sure it's the right one. + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth0.Status.Conditions[0].Type) + + if multiInterface { + // Fourth reconcile should create the second bpfProgram. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the Second BpfProgram Object was created successfully + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + require.NotEmpty(t, bpfProgEth1) + // owningConfig Label was correctly set + require.Equal(t, bpfProgEth1.Labels[internal.BpfProgramOwner], name) + // node Label was correctly set + require.Equal(t, bpfProgEth1.Labels[internal.K8sHostLabel], fakeNode.Name) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProgEth1.Finalizers[0]) + // Type is set + require.Equal(t, r.getRecType(), bpfProgEth1.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of bpfProgram with Fake UID since the fake API server won't + bpfProgEth1.UID = types.UID(fakeUID1) + err = cl.Update(ctx, bpfProgEth1) + require.NoError(t, err) + + // Fifth reconcile should create the second bpfProgram. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Sixth reconcile should update the second bpfProgram's status. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + uuid1 := string(bpfProgEth1.UID) + netns := fmt.Sprintf("/host/proc/%d/ns/net", fakePid) + + expectedLoadReq1 := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Xdp.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: uuid1, internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_XdpAttachInfo{ + XdpAttachInfo: &gobpfman.XDPAttachInfo{ + Priority: 0, + Iface: fakeInts[1], + ProceedOn: []int32{2, 31}, + Netns: &netns, + }, + }, + }, + } + + // Check that the bpfProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + // prog ID should already have been set + id1, err := GetID(bpfProgEth1) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly built + if !cmp.Equal(expectedLoadReq1, cli.LoadRequests[int(*id1)], protocmp.Transform()) { + t.Logf("Diff %v", cmp.Diff(expectedLoadReq1, cli.LoadRequests[int(*id1)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Get program object + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + // Check that the bpfProgram's status was correctly updated + // Make sure we only have 1 condition now + require.Equal(t, 1, len(bpfProgEth1.Status.Conditions)) + // Make sure it's the right one. + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth1.Status.Conditions[0].Type) + } +} + +func TestXdpNsProgramControllerCreate(t *testing.T) { + xdpNsProgramControllerCreate(t, false, false) +} + +func TestXdpNsProgramControllerCreateMultiIntf(t *testing.T) { + xdpNsProgramControllerCreate(t, true, false) +} + +func TestXdpNsUpdateStatus(t *testing.T) { + xdpNsProgramControllerCreate(t, false, true) +} diff --git a/controllers/bpfman-agent/xdp-program.go b/controllers/bpfman-agent/xdp-program.go index 364217908..07648ac7c 100644 --- a/controllers/bpfman-agent/xdp-program.go +++ b/controllers/bpfman-agent/xdp-program.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanagent import ( @@ -40,7 +42,7 @@ import ( // BpfProgramReconciler reconciles a BpfProgram object type XdpProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler currentXdpProgram *bpfmaniov1alpha1.XdpProgram interfaces []string ourNode *v1.Node @@ -70,6 +72,14 @@ func (r *XdpProgramReconciler) getName() string { return r.currentXdpProgram.Name } +func (r *XdpProgramReconciler) getNamespace() string { + return r.currentXdpProgram.Namespace +} + +func (r *XdpProgramReconciler) getNoContAnnotationIndex() string { + return internal.XdpNoContainersOnNode +} + func (r *XdpProgramReconciler) getNode() *v1.Node { return r.ourNode } @@ -168,7 +178,13 @@ func (r *XdpProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpf // There is a container selector, so see if there are any matching // containers on this node. - containerInfo, err := r.Containers.GetContainers(ctx, r.currentXdpProgram.Spec.Containers, r.Logger) + containerInfo, err := r.Containers.GetContainers( + ctx, + r.currentXdpProgram.Spec.Containers.Namespace, + r.currentXdpProgram.Spec.Containers.Pods, + r.currentXdpProgram.Spec.Containers.ContainerNames, + r.Logger, + ) if err != nil { return nil, fmt.Errorf("failed to get container pids: %v", err) } @@ -244,7 +260,7 @@ func (r *XdpProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) r.ourNode = &v1.Node{} r.Logger = ctrl.Log.WithName("xdp") - r.Logger.Info("bpfman-agent enter: XDP", "Name", req.Name) + r.Logger.Info("bpfman-agent enter: xdp", "Name", req.Name) // Lookup K8s node object for this bpfman-agent This should always succeed if err := r.Get(ctx, types.NamespacedName{Namespace: v1.NamespaceAll, Name: r.NodeName}, r.ourNode); err != nil { diff --git a/controllers/bpfman-agent/xdp-program_test.go b/controllers/bpfman-agent/xdp-program_test.go index 922cd28e4..e6378758c 100644 --- a/controllers/bpfman-agent/xdp-program_test.go +++ b/controllers/bpfman-agent/xdp-program_test.go @@ -24,7 +24,6 @@ import ( "google.golang.org/protobuf/testing/protocmp" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" internal "github.com/bpfman/bpfman-operator/internal" testutils "github.com/bpfman/bpfman-operator/internal/test-utils" @@ -115,18 +114,21 @@ func xdpProgramControllerCreate(t *testing.T, multiInterface bool, multiConditio cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &XdpProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &XdpProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -144,7 +146,7 @@ func xdpProgramControllerCreate(t *testing.T, multiInterface bool, multiConditio } // Check the first BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) require.NotEmpty(t, bpfProgEth0) @@ -195,11 +197,11 @@ func xdpProgramControllerCreate(t *testing.T, multiInterface bool, multiConditio } // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) // prog ID should already have been set - id0, err := bpfmanagentinternal.GetID(bpfProgEth0) + id0, err := GetID(bpfProgEth0) require.NoError(t, err) // Check the bpfLoadRequest was correctly built @@ -235,7 +237,7 @@ func xdpProgramControllerCreate(t *testing.T, multiInterface bool, multiConditio require.False(t, res.Requeue) // Get program object - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) // Check that the bpfProgram's status was correctly updated @@ -255,7 +257,7 @@ func xdpProgramControllerCreate(t *testing.T, multiInterface bool, multiConditio require.False(t, res.Requeue) // Check the Second BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) require.NotEmpty(t, bpfProgEth1) @@ -315,11 +317,11 @@ func xdpProgramControllerCreate(t *testing.T, multiInterface bool, multiConditio } // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) // prog ID should already have been set - id1, err := bpfmanagentinternal.GetID(bpfProgEth1) + id1, err := GetID(bpfProgEth1) require.NoError(t, err) // Check the bpfLoadRequest was correctly built @@ -329,7 +331,7 @@ func xdpProgramControllerCreate(t *testing.T, multiInterface bool, multiConditio } // Get program object - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) // Check that the bpfProgram's status was correctly updated diff --git a/controllers/bpfman-operator/application-ns-program_test.go b/controllers/bpfman-operator/application-ns-program_test.go new file mode 100644 index 000000000..770064549 --- /dev/null +++ b/controllers/bpfman-operator/application-ns-program_test.go @@ -0,0 +1,256 @@ +/* +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 bpfmanoperator + +import ( + "context" + "fmt" + "testing" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + internal "github.com/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + + "github.com/stretchr/testify/require" + meta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +// Runs the ApplicationProgramReconcile test. If multiCondition == true, it runs it +// with an error case in which the program object has multiple conditions. +func appNsProgramReconcile(t *testing.T, multiCondition bool) { + var ( + name = "fakeAppProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + bpfTcFunctionName = "tc_test" + bpfUprobeFunctionName = "uprobe_test" + bpfXdpFunctionName = "xdp-test" + fakeNode = testutils.NewNode("fake-control-plane") + tcFakeInt = "eth0" + tcDirection = "ingress" + uprobeFunctionName = "malloc" + uprobeTarget = "libc" + uprobeOffset = 0 + uprobeRetprobe = false + xdpFakeInt = "eth0" + ctx = context.TODO() + bpfProgName = fmt.Sprintf("%s-%s", name, fakeNode.Name) + ) + // A AppProgram object with metadata and spec. + App := &bpfmaniov1alpha1.BpfNsApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: bpfmaniov1alpha1.BpfNsApplicationSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + Programs: []bpfmaniov1alpha1.BpfNsApplicationProgram{ + { + Type: bpfmaniov1alpha1.ProgTypeTC, + TC: &bpfmaniov1alpha1.TcNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfTcFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &[]string{tcFakeInt}, + }, + Priority: 0, + Direction: tcDirection, + ProceedOn: []bpfmaniov1alpha1.TcProceedOnValue{ + bpfmaniov1alpha1.TcProceedOnValue("pipe"), + bpfmaniov1alpha1.TcProceedOnValue("dispatcher_return"), + }, + }, + }, + { + Type: bpfmaniov1alpha1.ProgTypeUprobe, + Uprobe: &bpfmaniov1alpha1.UprobeNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfUprobeFunctionName, + }, + FunctionName: uprobeFunctionName, + Target: uprobeTarget, + Offset: uint64(uprobeOffset), + RetProbe: uprobeRetprobe, + }, + /* + Containers: &bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + */ + }, + { + Type: bpfmaniov1alpha1.ProgTypeXDP, + XDP: &bpfmaniov1alpha1.XdpNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfXdpFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &[]string{xdpFakeInt}, + }, + Priority: 0, + ProceedOn: []bpfmaniov1alpha1.XdpProceedOnValue{bpfmaniov1alpha1.XdpProceedOnValue("pass"), + bpfmaniov1alpha1.XdpProceedOnValue("dispatcher_return"), + }, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + }, + }, + }, + }, + } + + // The expected accompanying BpfNsProgram object + expectedBpfProg := &bpfmaniov1alpha1.BpfNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: bpfProgName, + Namespace: namespace, + OwnerReferences: []metav1.OwnerReference{ + { + Name: App.Name, + Controller: &[]bool{true}[0], + }, + }, + Labels: map[string]string{internal.BpfProgramOwner: App.Name, internal.K8sHostLabel: fakeNode.Name}, + Finalizers: []string{internal.BpfNsApplicationControllerFinalizer}, + }, + Spec: bpfmaniov1alpha1.BpfProgramSpec{ + Type: "application", + }, + Status: bpfmaniov1alpha1.BpfProgramStatus{ + Conditions: []metav1.Condition{bpfmaniov1alpha1.BpfProgCondLoaded.Condition()}, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, App, expectedBpfProg} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, App) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(App).WithRuntimeObjects(objs...).Build() + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + } + + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ApplicationProgram object with the scheme and fake client. + r := &BpfNsApplicationReconciler{NamespaceProgramReconciler: npr} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should add the finalizer to the applicationProgram object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: App.Name, Namespace: App.Namespace}, App) + require.NoError(t, err) + + // Check the bpfman-operator finalizer was successfully added + require.Contains(t, App.GetFinalizers(), internal.BpfmanOperatorFinalizer) + + // NOTE: THIS IS A TEST FOR AN ERROR PATH. THERE SHOULD NEVER BE MORE THAN + // ONE CONDITION. + if multiCondition { + // Add some random conditions and verify that the condition still gets + // updated correctly. + meta.SetStatusCondition(&App.Status.Conditions, bpfmaniov1alpha1.ProgramDeleteError.Condition("bogus condition #1")) + if err := r.Status().Update(ctx, App); err != nil { + r.Logger.V(1).Info("failed to set App Ns Program object status") + } + meta.SetStatusCondition(&App.Status.Conditions, bpfmaniov1alpha1.ProgramReconcileError.Condition("bogus condition #2")) + if err := r.Status().Update(ctx, App); err != nil { + r.Logger.V(1).Info("failed to set App Ns Program object status") + } + // Make sure we have 2 conditions + require.Equal(t, 2, len(App.Status.Conditions)) + } + + // Second reconcile should check BpfNsProgram Status and write Success condition to TcNsProgram Status + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: App.Name, Namespace: App.Namespace}, App) + require.NoError(t, err) + + // Make sure we only have 1 condition now + require.Equal(t, 1, len(App.Status.Conditions)) + // Make sure it's the right one. + require.Equal(t, App.Status.Conditions[0].Type, string(bpfmaniov1alpha1.ProgramReconcileSuccess)) +} + +func TestAppNsProgramReconcile(t *testing.T) { + appNsProgramReconcile(t, false) +} + +func TestAppNsUpdateStatus(t *testing.T) { + appNsProgramReconcile(t, true) +} diff --git a/controllers/bpfman-operator/application-ns-programs.go b/controllers/bpfman-operator/application-ns-programs.go new file mode 100644 index 000000000..f56f669ce --- /dev/null +++ b/controllers/bpfman-operator/application-ns-programs.go @@ -0,0 +1,141 @@ +/* +Copyright 2024 The bpfman Authors. + +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 bpfmanoperator + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + internal "github.com/bpfman/bpfman-operator/internal" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfnsapplications,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfnsapplications/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfnsapplications/finalizers,verbs=update +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=bpfnsapplications,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=bpfnsapplications/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=bpfnsapplications/finalizers,verbs=update + +// BpfNsApplicationReconciler reconciles a BpfNsApplication object +type BpfNsApplicationReconciler struct { + NamespaceProgramReconciler +} + +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *BpfNsApplicationReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList] { + return &r.NamespaceProgramReconciler.ReconcilerCommon +} + +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *BpfNsApplicationReconciler) getFinalizer() string { + return internal.BpfNsApplicationControllerFinalizer +} + +// SetupWithManager sets up the controller with the Manager. +func (r *BpfNsApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.BpfNsApplication{}). + // Watch BpfNsPrograms which are owned by BpfNsApplications + Watches( + &bpfmaniov1alpha1.BpfNsProgram{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates( + predicate.And( + statusChangedPredicateNamespace(), + internal.BpfNsProgramTypePredicate(internal.ApplicationString), + ), + ), + ). + Complete(r) +} + +func (r *BpfNsApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Logger = ctrl.Log.WithName("application") + r.Logger.Info("bpfman-operator enter: application-ns", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + + appProgram := &bpfmaniov1alpha1.BpfNsApplication{} + if err := r.Get(ctx, req.NamespacedName, appProgram); err != nil { + // Reconcile was triggered by BpfNsProgram event, get parent appProgram Object. + if errors.IsNotFound(err) { + bpfProgram := &bpfmaniov1alpha1.BpfNsProgram{} + if err := r.Get(ctx, req.NamespacedName, bpfProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.V(1).Info("BpfNsProgram not found stale reconcile, exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting BpfNsProgram Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + // Get owning appProgram object from ownerRef + ownerRef := metav1.GetControllerOf(bpfProgram) + if ownerRef == nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting BpfNsProgram Object owner") + } + + if err := r.Get(ctx, types.NamespacedName{Namespace: req.NamespacedName.Namespace, Name: ownerRef.Name}, appProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.Info("Application Programs from ownerRef not found stale reconcile exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting Application Programs Object from ownerRef", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + } else { + r.Logger.Error(err, "failed getting Application Programs Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + return ctrl.Result{}, nil + } + } + + return reconcileBpfProgram(ctx, r, appProgram) +} + +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *BpfNsApplicationReconciler) updateStatus( + ctx context.Context, + namespace string, + name string, + cond bpfmaniov1alpha1.ProgramConditionType, + message string, +) (ctrl.Result, error) { + // Sometimes we end up with a stale FentryProgram due to races, do this + // get to ensure we're up to date before attempting a status update. + app := &bpfmaniov1alpha1.BpfNsApplication{} + if err := r.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, app); err != nil { + r.Logger.V(1).Info("failed to get fresh Application Programs object...requeuing") + return ctrl.Result{Requeue: true, RequeueAfter: retryDurationOperator}, nil + } + + return r.updateCondition(ctx, app, &app.Status.Conditions, cond, message) +} diff --git a/controllers/bpfman-operator/application-program_test.go b/controllers/bpfman-operator/application-program_test.go index 76fc0770f..a2f3d2a25 100644 --- a/controllers/bpfman-operator/application-program_test.go +++ b/controllers/bpfman-operator/application-program_test.go @@ -135,16 +135,20 @@ func appProgramReconcile(t *testing.T, multiCondition bool) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithStatusSubresource(App).WithRuntimeObjects(objs...).Build() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ApplicationProgram object with the scheme and fake client. - r := &BpfApplicationReconciler{ReconcilerCommon: rc} + r := &BpfApplicationReconciler{ClusterProgramReconciler: cpr} // Mock request to simulate Reconcile() being called on an event for a // watched resource . diff --git a/controllers/bpfman-operator/application-programs.go b/controllers/bpfman-operator/application-programs.go index 58fb7ecc7..2be810839 100644 --- a/controllers/bpfman-operator/application-programs.go +++ b/controllers/bpfman-operator/application-programs.go @@ -40,13 +40,15 @@ import ( // BpfApplicationReconciler reconciles a BpfApplication object type BpfApplicationReconciler struct { - ReconcilerCommon + ClusterProgramReconciler } -func (r *BpfApplicationReconciler) getRecCommon() *ReconcilerCommon { - return &r.ReconcilerCommon +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *BpfApplicationReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] { + return &r.ClusterProgramReconciler.ReconcilerCommon } +//lint:ignore U1000 Linter claims function unused, but generics confusing linter func (r *BpfApplicationReconciler) getFinalizer() string { return internal.BpfApplicationControllerFinalizer } @@ -59,7 +61,7 @@ func (r *BpfApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &bpfmaniov1alpha1.BpfProgram{}, &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.ApplicationString))), + builder.WithPredicates(predicate.And(statusChangedPredicateCluster(), internal.BpfProgramTypePredicate(internal.ApplicationString))), ). Complete(r) } @@ -106,7 +108,8 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque return reconcileBpfProgram(ctx, r, appProgram) } -func (r *BpfApplicationReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *BpfApplicationReconciler) updateStatus(ctx context.Context, _namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { // Sometimes we end up with a stale FentryProgram due to races, do this // get to ensure we're up to date before attempting a status update. app := &bpfmaniov1alpha1.BpfApplication{} diff --git a/controllers/bpfman-operator/common.go b/controllers/bpfman-operator/common.go index 3c7d9ec1c..38061e02d 100644 --- a/controllers/bpfman-operator/common.go +++ b/controllers/bpfman-operator/common.go @@ -19,7 +19,6 @@ package bpfmanoperator import ( "context" "fmt" - "reflect" "time" corev1 "k8s.io/api/core/v1" @@ -29,8 +28,6 @@ import ( 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/event" - "sigs.k8s.io/controller-runtime/pkg/predicate" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" internal "github.com/bpfman/bpfman-operator/internal" @@ -39,47 +36,73 @@ import ( ) //+kubebuilder:rbac:groups=bpfman.io,resources=bpfprograms,verbs=get;list;watch -//+kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfnsprograms,verbs=get;list;watch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=bpfnsprograms,verbs=get;list;watch +// +kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch const ( retryDurationOperator = 5 * time.Second ) +type BpfProgOper interface { + GetName() string + + GetLabels() map[string]string + GetStatus() *bpfmaniov1alpha1.BpfProgramStatus +} + +type BpfProgListOper[T any] interface { + // bpfmniov1alpha1.BpfProgramList | bpfmaniov1alpha1.BpfNsProgramList + + GetItems() []T +} + // ReconcilerCommon reconciles a BpfProgram object -type ReconcilerCommon struct { +type ReconcilerCommon[T BpfProgOper, TL BpfProgListOper[T]] struct { client.Client Scheme *runtime.Scheme Logger logr.Logger } // bpfmanReconciler defines a k8s reconciler which can program bpfman. -type ProgramReconciler interface { - getRecCommon() *ReconcilerCommon +type ProgramReconciler[T BpfProgOper, TL BpfProgListOper[T]] interface { + // BPF Cluster of Namespaced Reconciler + getBpfList(ctx context.Context, + progName string, + progNamespace string, + ) (*TL, error) + containsFinalizer(bpfProgram *T, finalizer string) bool + + // *Program Reconciler + getRecCommon() *ReconcilerCommon[T, TL] updateStatus(ctx context.Context, + namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) getFinalizer() string } -func reconcileBpfProgram(ctx context.Context, rec ProgramReconciler, prog client.Object) (ctrl.Result, error) { +func reconcileBpfProgram[T BpfProgOper, TL BpfProgListOper[T]]( + ctx context.Context, + rec ProgramReconciler[T, TL], + prog client.Object, +) (ctrl.Result, error) { r := rec.getRecCommon() progName := prog.GetName() + progNamespace := prog.GetNamespace() - r.Logger.V(1).Info("Reconciling Program", "Name", progName) + r.Logger.V(1).Info("Reconciling Program", "Namespace", progNamespace, "Name", progName) if !controllerutil.ContainsFinalizer(prog, internal.BpfmanOperatorFinalizer) { + r.Logger.V(1).Info("Add Finalizer", "Namespace", progNamespace, "ProgramName", progName) return r.addFinalizer(ctx, prog, internal.BpfmanOperatorFinalizer) } // reconcile Program Object on all other events // list all existing bpfProgram state for the given Program - bpfPrograms := &bpfmaniov1alpha1.BpfProgramList{} - - // Only list bpfPrograms for this Program - opts := []client.ListOption{client.MatchingLabels{internal.BpfProgramOwner: progName}} - - if err := r.List(ctx, bpfPrograms, opts...); err != nil { + bpfPrograms, err := rec.getBpfList(ctx, progName, progNamespace) + if err != nil { r.Logger.Error(err, "failed to get freshPrograms for full reconcile") return ctrl.Result{}, nil } @@ -96,7 +119,7 @@ func reconcileBpfProgram(ctx context.Context, rec ProgramReconciler, prog client if prog.GetDeletionTimestamp().IsZero() { for _, node := range nodes.Items { nodeFound := false - for _, program := range bpfPrograms.Items { + for _, program := range (*bpfPrograms).GetItems() { bpfProgramNode := program.GetLabels()[internal.K8sHostLabel] if node.Name == bpfProgramNode { nodeFound = true @@ -104,7 +127,7 @@ func reconcileBpfProgram(ctx context.Context, rec ProgramReconciler, prog client } } if !nodeFound { - return rec.updateStatus(ctx, progName, bpfmaniov1alpha1.ProgramNotYetLoaded, "") + return rec.updateStatus(ctx, progNamespace, progName, bpfmaniov1alpha1.ProgramNotYetLoaded, "") } } } @@ -112,14 +135,15 @@ func reconcileBpfProgram(ctx context.Context, rec ProgramReconciler, prog client failedBpfPrograms := []string{} finalApplied := []string{} // Make sure no bpfPrograms had any issues in the loading or unloading process - for _, bpfProgram := range bpfPrograms.Items { + for _, bpfProgram := range (*bpfPrograms).GetItems() { - if controllerutil.ContainsFinalizer(&bpfProgram, rec.getFinalizer()) { - finalApplied = append(finalApplied, bpfProgram.Name) + if rec.containsFinalizer(&bpfProgram, rec.getFinalizer()) { + finalApplied = append(finalApplied, bpfProgram.GetName()) } - if bpfmanHelpers.IsBpfProgramConditionFailure(&bpfProgram.Status.Conditions) { - failedBpfPrograms = append(failedBpfPrograms, bpfProgram.Name) + status := bpfProgram.GetStatus() + if bpfmanHelpers.IsBpfProgramConditionFailure(&status.Conditions) { + failedBpfPrograms = append(failedBpfPrograms, bpfProgram.GetName()) } } @@ -132,21 +156,21 @@ func reconcileBpfProgram(ctx context.Context, rec ProgramReconciler, prog client } // Causes Requeue - return rec.updateStatus(ctx, progName, bpfmaniov1alpha1.ProgramDeleteError, fmt.Sprintf("Program Deletion failed on the following bpfProgram Objects: %v", - finalApplied)) + return rec.updateStatus(ctx, progNamespace, progName, bpfmaniov1alpha1.ProgramDeleteError, + fmt.Sprintf("Program Deletion failed on the following bpfProgram Objects: %v", finalApplied)) } if len(failedBpfPrograms) != 0 { // Causes Requeue - return rec.updateStatus(ctx, progName, bpfmaniov1alpha1.ProgramReconcileError, + return rec.updateStatus(ctx, progNamespace, progName, bpfmaniov1alpha1.ProgramReconcileError, fmt.Sprintf("bpfProgramReconciliation failed on the following bpfProgram Objects: %v", failedBpfPrograms)) } // Causes Requeue - return rec.updateStatus(ctx, progName, bpfmaniov1alpha1.ProgramReconcileSuccess, "") + return rec.updateStatus(ctx, progNamespace, progName, bpfmaniov1alpha1.ProgramReconcileSuccess, "") } -func (r *ReconcilerCommon) removeFinalizer(ctx context.Context, prog client.Object, finalizer string) (ctrl.Result, error) { +func (r *ReconcilerCommon[T, TL]) removeFinalizer(ctx context.Context, prog client.Object, finalizer string) (ctrl.Result, error) { r.Logger.Info("Calling KubeAPI to delete Program Finalizer", "Type", prog.GetObjectKind().GroupVersionKind().Kind, "Name", prog.GetName()) if changed := controllerutil.RemoveFinalizer(prog, finalizer); changed { @@ -160,7 +184,7 @@ func (r *ReconcilerCommon) removeFinalizer(ctx context.Context, prog client.Obje return ctrl.Result{}, nil } -func (r *ReconcilerCommon) addFinalizer(ctx context.Context, prog client.Object, finalizer string) (ctrl.Result, error) { +func (r *ReconcilerCommon[T, TL]) addFinalizer(ctx context.Context, prog client.Object, finalizer string) (ctrl.Result, error) { controllerutil.AddFinalizer(prog, finalizer) r.Logger.Info("Calling KubeAPI to add Program Finalizer", "Type", prog.GetObjectKind().GroupVersionKind().Kind, "Name", prog.GetName()) @@ -173,27 +197,13 @@ func (r *ReconcilerCommon) addFinalizer(ctx context.Context, prog client.Object, return ctrl.Result{}, nil } -// Only reconcile if a bpfprogram object's status has been updated. -func statusChangedPredicate() predicate.Funcs { - return predicate.Funcs{ - GenericFunc: func(e event.GenericEvent) bool { - return false - }, - CreateFunc: func(e event.CreateEvent) bool { - return false - }, - UpdateFunc: func(e event.UpdateEvent) bool { - oldObject := e.ObjectOld.(*bpfmaniov1alpha1.BpfProgram) - newObject := e.ObjectNew.(*bpfmaniov1alpha1.BpfProgram) - return !reflect.DeepEqual(oldObject.Status, newObject.Status) - }, - DeleteFunc: func(e event.DeleteEvent) bool { - return false - }, - } -} - -func (r *ReconcilerCommon) updateCondition(ctx context.Context, obj client.Object, conditions *[]metav1.Condition, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +func (r *ReconcilerCommon[T, TL]) updateCondition( + ctx context.Context, + obj client.Object, + conditions *[]metav1.Condition, + cond bpfmaniov1alpha1.ProgramConditionType, + message string, +) (ctrl.Result, error) { r.Logger.V(1).Info("updateCondition()", "existing conds", conditions, "new cond", cond) diff --git a/controllers/bpfman-operator/common_cluster.go b/controllers/bpfman-operator/common_cluster.go new file mode 100644 index 000000000..1e1a6cb06 --- /dev/null +++ b/controllers/bpfman-operator/common_cluster.go @@ -0,0 +1,83 @@ +/* +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 bpfmanoperator + +import ( + "context" + "reflect" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + internal "github.com/bpfman/bpfman-operator/internal" +) + +type ClusterProgramReconciler struct { + ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] +} + +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *ClusterProgramReconciler) getBpfList( + ctx context.Context, + progName string, + _progNamespace string, +) (*bpfmaniov1alpha1.BpfProgramList, error) { + + bpfProgramList := &bpfmaniov1alpha1.BpfProgramList{} + + // Only list bpfPrograms for this Program + opts := []client.ListOption{ + client.MatchingLabels{internal.BpfProgramOwner: progName}, + } + + err := r.List(ctx, bpfProgramList, opts...) + if err != nil { + return nil, err + } + + return bpfProgramList, nil +} + +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *ClusterProgramReconciler) containsFinalizer( + bpfProgram *bpfmaniov1alpha1.BpfProgram, + finalizer string, +) bool { + return controllerutil.ContainsFinalizer(bpfProgram, finalizer) +} + +func statusChangedPredicateCluster() predicate.Funcs { + return predicate.Funcs{ + GenericFunc: func(e event.GenericEvent) bool { + return false + }, + CreateFunc: func(e event.CreateEvent) bool { + return false + }, + UpdateFunc: func(e event.UpdateEvent) bool { + oldObject := e.ObjectOld.(*bpfmaniov1alpha1.BpfProgram) + newObject := e.ObjectNew.(*bpfmaniov1alpha1.BpfProgram) + return !reflect.DeepEqual(oldObject.GetStatus(), newObject.Status) + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return false + }, + } +} diff --git a/controllers/bpfman-operator/common_namespace.go b/controllers/bpfman-operator/common_namespace.go new file mode 100644 index 000000000..a3dc65677 --- /dev/null +++ b/controllers/bpfman-operator/common_namespace.go @@ -0,0 +1,84 @@ +/* +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 bpfmanoperator + +import ( + "context" + "reflect" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + internal "github.com/bpfman/bpfman-operator/internal" +) + +type NamespaceProgramReconciler struct { + ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList] +} + +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *NamespaceProgramReconciler) getBpfList( + ctx context.Context, + progName string, + progNamespace string, +) (*bpfmaniov1alpha1.BpfNsProgramList, error) { + + bpfProgramList := &bpfmaniov1alpha1.BpfNsProgramList{} + + // Only list bpfPrograms for this Program + opts := []client.ListOption{ + client.MatchingLabels{internal.BpfProgramOwner: progName}, + client.InNamespace(progNamespace), + } + + err := r.List(ctx, bpfProgramList, opts...) + if err != nil { + return nil, err + } + + return bpfProgramList, nil +} + +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *NamespaceProgramReconciler) containsFinalizer( + bpfProgram *bpfmaniov1alpha1.BpfNsProgram, + finalizer string, +) bool { + return controllerutil.ContainsFinalizer(bpfProgram, finalizer) +} + +func statusChangedPredicateNamespace() predicate.Funcs { + return predicate.Funcs{ + GenericFunc: func(e event.GenericEvent) bool { + return false + }, + CreateFunc: func(e event.CreateEvent) bool { + return false + }, + UpdateFunc: func(e event.UpdateEvent) bool { + oldObject := e.ObjectOld.(*bpfmaniov1alpha1.BpfNsProgram) + newObject := e.ObjectNew.(*bpfmaniov1alpha1.BpfNsProgram) + return !reflect.DeepEqual(oldObject.GetStatus(), newObject.Status) + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return false + }, + } +} diff --git a/controllers/bpfman-operator/configmap.go b/controllers/bpfman-operator/configmap.go index a351eca7c..fb254a559 100644 --- a/controllers/bpfman-operator/configmap.go +++ b/controllers/bpfman-operator/configmap.go @@ -48,7 +48,7 @@ import ( // +kubebuilder:rbac:groups=bpfman.io,resources=configmaps/finalizers,verbs=update type BpfmanConfigReconciler struct { - ReconcilerCommon + ClusterProgramReconciler BpfmanStandardDeployment string CsiDriverDeployment string RestrictedSCC string diff --git a/controllers/bpfman-operator/configmap_test.go b/controllers/bpfman-operator/configmap_test.go index 8bb153eae..9b1bbb7c0 100644 --- a/controllers/bpfman-operator/configmap_test.go +++ b/controllers/bpfman-operator/configmap_test.go @@ -40,6 +40,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/reconcile" + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" "github.com/bpfman/bpfman-operator/internal" ) @@ -92,10 +93,19 @@ func setupTestEnvironment(isOpenShift bool) (*BpfmanConfigReconciler, *corev1.Co // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ + Client: cl, + Scheme: s, + } + + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Create a BpfmanConfigReconciler object with the scheme and // fake client. r := &BpfmanConfigReconciler{ - ReconcilerCommon: ReconcilerCommon{Client: cl, Scheme: s}, + ClusterProgramReconciler: cpr, BpfmanStandardDeployment: resolveConfigPath(internal.BpfmanDaemonManifestPath), CsiDriverDeployment: resolveConfigPath(internal.BpfmanCsiDriverPath), IsOpenshift: isOpenShift, diff --git a/controllers/bpfman-operator/fentry-program.go b/controllers/bpfman-operator/fentry-program.go index f7c5c7ab0..feec90b34 100644 --- a/controllers/bpfman-operator/fentry-program.go +++ b/controllers/bpfman-operator/fentry-program.go @@ -11,6 +11,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanoperator import ( @@ -35,11 +37,11 @@ import ( //+kubebuilder:rbac:groups=bpfman.io,resources=fentryprograms/finalizers,verbs=update type FentryProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler } -func (r *FentryProgramReconciler) getRecCommon() *ReconcilerCommon { - return &r.ReconcilerCommon +func (r *FentryProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] { + return &r.ClusterProgramReconciler.ReconcilerCommon } func (r *FentryProgramReconciler) getFinalizer() string { @@ -54,7 +56,7 @@ func (r *FentryProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &bpfmaniov1alpha1.BpfProgram{}, &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.FentryString))), + builder.WithPredicates(predicate.And(statusChangedPredicateCluster(), internal.BpfProgramTypePredicate(internal.FentryString))), ). Complete(r) } @@ -101,7 +103,7 @@ func (r *FentryProgramReconciler) Reconcile(ctx context.Context, req ctrl.Reques return reconcileBpfProgram(ctx, r, fentryProgram) } -func (r *FentryProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +func (r *FentryProgramReconciler) updateStatus(ctx context.Context, _namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { // Sometimes we end up with a stale FentryProgram due to races, do this // get to ensure we're up to date before attempting a status update. prog := &bpfmaniov1alpha1.FentryProgram{} diff --git a/controllers/bpfman-operator/fentry-program_test.go b/controllers/bpfman-operator/fentry-program_test.go index 1ffd18af7..335894de0 100644 --- a/controllers/bpfman-operator/fentry-program_test.go +++ b/controllers/bpfman-operator/fentry-program_test.go @@ -105,16 +105,19 @@ func fentryProgramReconcile(t *testing.T, multiCondition bool) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithStatusSubresource(Fentry).WithRuntimeObjects(objs...).Build() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a FentryProgram object with the scheme and fake client. - r := &FentryProgramReconciler{ReconcilerCommon: rc} + r := &FentryProgramReconciler{ClusterProgramReconciler: cpr} // Mock request to simulate Reconcile() being called on an event for a // watched resource . diff --git a/controllers/bpfman-operator/fexit-program.go b/controllers/bpfman-operator/fexit-program.go index 7a5ec42c9..19eafd8a3 100644 --- a/controllers/bpfman-operator/fexit-program.go +++ b/controllers/bpfman-operator/fexit-program.go @@ -11,6 +11,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanoperator import ( @@ -35,11 +37,11 @@ import ( //+kubebuilder:rbac:groups=bpfman.io,resources=fexitprograms/finalizers,verbs=update type FexitProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler } -func (r *FexitProgramReconciler) getRecCommon() *ReconcilerCommon { - return &r.ReconcilerCommon +func (r *FexitProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] { + return &r.ClusterProgramReconciler.ReconcilerCommon } func (r *FexitProgramReconciler) getFinalizer() string { @@ -54,7 +56,7 @@ func (r *FexitProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &bpfmaniov1alpha1.BpfProgram{}, &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.FexitString))), + builder.WithPredicates(predicate.And(statusChangedPredicateCluster(), internal.BpfProgramTypePredicate(internal.FexitString))), ). Complete(r) } @@ -101,7 +103,7 @@ func (r *FexitProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request return reconcileBpfProgram(ctx, r, fexitProgram) } -func (r *FexitProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +func (r *FexitProgramReconciler) updateStatus(ctx context.Context, _namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { // Sometimes we end up with a stale FexitProgram due to races, do this // get to ensure we're up to date before attempting a status update. prog := &bpfmaniov1alpha1.FexitProgram{} diff --git a/controllers/bpfman-operator/fexit-program_test.go b/controllers/bpfman-operator/fexit-program_test.go index 086e30d9d..d2077ece4 100644 --- a/controllers/bpfman-operator/fexit-program_test.go +++ b/controllers/bpfman-operator/fexit-program_test.go @@ -105,16 +105,20 @@ func fexitProgramReconcile(t *testing.T, multiCondition bool) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithStatusSubresource(Fexit).WithRuntimeObjects(objs...).Build() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a FexitProgram object with the scheme and fake client. - r := &FexitProgramReconciler{ReconcilerCommon: rc} + r := &FexitProgramReconciler{ClusterProgramReconciler: cpr} // Mock request to simulate Reconcile() being called on an event for a // watched resource . diff --git a/controllers/bpfman-operator/kprobe-program.go b/controllers/bpfman-operator/kprobe-program.go index 71623e6e2..2cfa6b6e0 100644 --- a/controllers/bpfman-operator/kprobe-program.go +++ b/controllers/bpfman-operator/kprobe-program.go @@ -11,6 +11,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanoperator import ( @@ -35,11 +37,11 @@ import ( //+kubebuilder:rbac:groups=bpfman.io,resources=kprobeprograms/finalizers,verbs=update type KprobeProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler } -func (r *KprobeProgramReconciler) getRecCommon() *ReconcilerCommon { - return &r.ReconcilerCommon +func (r *KprobeProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] { + return &r.ClusterProgramReconciler.ReconcilerCommon } func (r *KprobeProgramReconciler) getFinalizer() string { @@ -54,7 +56,7 @@ func (r *KprobeProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &bpfmaniov1alpha1.BpfProgram{}, &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.Kprobe.String()))), + builder.WithPredicates(predicate.And(statusChangedPredicateCluster(), internal.BpfProgramTypePredicate(internal.Kprobe.String()))), ). Complete(r) } @@ -101,7 +103,7 @@ func (r *KprobeProgramReconciler) Reconcile(ctx context.Context, req ctrl.Reques return reconcileBpfProgram(ctx, r, kprobeProgram) } -func (r *KprobeProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +func (r *KprobeProgramReconciler) updateStatus(ctx context.Context, _namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { // Sometimes we end up with a stale KprobeProgram due to races, do this // get to ensure we're up to date before attempting a status update. prog := &bpfmaniov1alpha1.KprobeProgram{} diff --git a/controllers/bpfman-operator/kprobe-program_test.go b/controllers/bpfman-operator/kprobe-program_test.go index 5216fbd77..c0864dc58 100644 --- a/controllers/bpfman-operator/kprobe-program_test.go +++ b/controllers/bpfman-operator/kprobe-program_test.go @@ -109,16 +109,20 @@ func kprobeProgramReconcile(t *testing.T, multiCondition bool) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithStatusSubresource(Kprobe).WithRuntimeObjects(objs...).Build() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a KprobeProgram object with the scheme and fake client. - r := &KprobeProgramReconciler{ReconcilerCommon: rc} + r := &KprobeProgramReconciler{ClusterProgramReconciler: cpr} // Mock request to simulate Reconcile() being called on an event for a // watched resource . diff --git a/controllers/bpfman-operator/tc-ns-program.go b/controllers/bpfman-operator/tc-ns-program.go new file mode 100644 index 000000000..69961786b --- /dev/null +++ b/controllers/bpfman-operator/tc-ns-program.go @@ -0,0 +1,134 @@ +/* +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. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanoperator + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman-operator/internal" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +type TcNsProgramReconciler struct { + NamespaceProgramReconciler +} + +func (r *TcNsProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList] { + return &r.NamespaceProgramReconciler.ReconcilerCommon +} + +func (r *TcNsProgramReconciler) getFinalizer() string { + return internal.TcNsProgramControllerFinalizer +} + +// SetupWithManager sets up the controller with the Manager. +func (r *TcNsProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.TcNsProgram{}). + // Watch BpfNsPrograms which are owned by TcNsPrograms + Watches( + &bpfmaniov1alpha1.BpfNsProgram{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates( + predicate.And( + statusChangedPredicateNamespace(), + internal.BpfNsProgramTypePredicate(internal.Tc.String()), + ), + ), + ). + Complete(r) +} + +//+kubebuilder:rbac:groups=bpfman.io,resources=tcnsprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,resources=tcnsprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,resources=tcnsprograms/finalizers,verbs=update +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=tcnsprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=tcnsprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=tcnsprograms/finalizers,verbs=update + +func (r *TcNsProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Logger = ctrl.Log.WithName("tc-ns") + r.Logger.Info("bpfman-operator enter: tc-ns", "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + + tcProgram := &bpfmaniov1alpha1.TcNsProgram{} + if err := r.Get(ctx, req.NamespacedName, tcProgram); err != nil { + // list all TcNsProgram objects with + if errors.IsNotFound(err) { + bpfProgram := &bpfmaniov1alpha1.BpfNsProgram{} + if err := r.Get(ctx, req.NamespacedName, bpfProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.V(1).Info("BpfNsProgram not found stale reconcile, exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting BpfNsProgram Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + // Get owning TcNsProgram object from ownerRef + ownerRef := metav1.GetControllerOf(bpfProgram) + if ownerRef == nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting BpfNsProgram Object owner") + } + + if err := r.Get(ctx, types.NamespacedName{Namespace: req.NamespacedName.Namespace, Name: ownerRef.Name}, tcProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.Info("TcNsProgram from ownerRef not found stale reconcile exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting TcNsProgram Object from ownerRef", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + } else { + r.Logger.Error(err, "failed getting TcNsProgram Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + return ctrl.Result{}, nil + } + } + + return reconcileBpfProgram(ctx, r, tcProgram) +} + +func (r *TcNsProgramReconciler) updateStatus( + ctx context.Context, + namespace string, + name string, + cond bpfmaniov1alpha1.ProgramConditionType, + message string, +) (ctrl.Result, error) { + // Sometimes we end up with a stale TcNsProgram due to races, do this + // get to ensure we're up to date before attempting a finalizer removal. + prog := &bpfmaniov1alpha1.TcNsProgram{} + if err := r.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, prog); err != nil { + r.Logger.V(1).Info("failed to get fresh TcNsProgram object...requeuing") + return ctrl.Result{Requeue: true, RequeueAfter: retryDurationOperator}, nil + } + + return r.updateCondition(ctx, prog, &prog.Status.Conditions, cond, message) +} diff --git a/controllers/bpfman-operator/tc-ns-program_test.go b/controllers/bpfman-operator/tc-ns-program_test.go new file mode 100644 index 000000000..125a3e7c5 --- /dev/null +++ b/controllers/bpfman-operator/tc-ns-program_test.go @@ -0,0 +1,176 @@ +/* +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 bpfmanoperator + +import ( + "context" + "fmt" + "testing" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestTcNsProgramReconcile(t *testing.T) { + var ( + name = "fakeTcNsProgram" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + direction = "ingress" + fakeNode = testutils.NewNode("fake-control-plane") + fakeInt = "eth0" + ctx = context.TODO() + bpfProgName = fmt.Sprintf("%s-%s", name, fakeNode.Name) + ) + + // A TcNsProgram object with metadata and spec. + tc := &bpfmaniov1alpha1.TcNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: bpfmaniov1alpha1.TcNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + TcNsProgramInfo: bpfmaniov1alpha1.TcNsProgramInfo{ + + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &[]string{fakeInt}, + }, + Priority: 0, + Direction: direction, + ProceedOn: []bpfmaniov1alpha1.TcProceedOnValue{ + bpfmaniov1alpha1.TcProceedOnValue("pipe"), + bpfmaniov1alpha1.TcProceedOnValue("dispatcher_return"), + }, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + }, + }, + } + + // The expected accompanying BpfNsProgram object + expectedBpfProg := &bpfmaniov1alpha1.BpfNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: bpfProgName, + OwnerReferences: []metav1.OwnerReference{ + { + Name: tc.Name, + Controller: &[]bool{true}[0], + }, + }, + Labels: map[string]string{internal.BpfProgramOwner: tc.Name, internal.K8sHostLabel: fakeNode.Name}, + Finalizers: []string{internal.TcNsProgramControllerFinalizer}, + }, + Spec: bpfmaniov1alpha1.BpfProgramSpec{ + Type: "tc", + }, + Status: bpfmaniov1alpha1.BpfProgramStatus{ + Conditions: []metav1.Condition{bpfmaniov1alpha1.BpfProgCondLoaded.Condition()}, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, tc, expectedBpfProg} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, tc) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(tc).WithRuntimeObjects(objs...).Build() + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + } + + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &TcNsProgramReconciler{NamespaceProgramReconciler: npr} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + }, + } + + // First reconcile should add the finalzier to the TcNsProgram object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: tc.Name, Namespace: tc.Namespace}, tc) + require.NoError(t, err) + + // Check the bpfman-operator finalizer was successfully added + require.Contains(t, tc.GetFinalizers(), internal.BpfmanOperatorFinalizer) + + // Second reconcile should check BpfNsProgram Status and write Success condition to TcNsProgram Status + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: tc.Name, Namespace: tc.Namespace}, tc) + require.NoError(t, err) + + require.Equal(t, tc.Status.Conditions[0].Type, string(bpfmaniov1alpha1.ProgramReconcileSuccess)) + +} diff --git a/controllers/bpfman-operator/tc-program.go b/controllers/bpfman-operator/tc-program.go index f8c5db136..5719762f1 100644 --- a/controllers/bpfman-operator/tc-program.go +++ b/controllers/bpfman-operator/tc-program.go @@ -11,6 +11,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanoperator import ( @@ -31,11 +33,11 @@ import ( ) type TcProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler } -func (r *TcProgramReconciler) getRecCommon() *ReconcilerCommon { - return &r.ReconcilerCommon +func (r *TcProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] { + return &r.ClusterProgramReconciler.ReconcilerCommon } func (r *TcProgramReconciler) getFinalizer() string { @@ -50,7 +52,7 @@ func (r *TcProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &bpfmaniov1alpha1.BpfProgram{}, &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.Tc.String()))), + builder.WithPredicates(predicate.And(statusChangedPredicateCluster(), internal.BpfProgramTypePredicate(internal.Tc.String()))), ). Complete(r) } @@ -101,7 +103,7 @@ func (r *TcProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return reconcileBpfProgram(ctx, r, tcProgram) } -func (r *TcProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +func (r *TcProgramReconciler) updateStatus(ctx context.Context, _namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { // Sometimes we end up with a stale TcProgram due to races, do this // get to ensure we're up to date before attempting a finalizer removal. prog := &bpfmaniov1alpha1.TcProgram{} diff --git a/controllers/bpfman-operator/tc-program_test.go b/controllers/bpfman-operator/tc-program_test.go index 032a4d7f8..8226e95a5 100644 --- a/controllers/bpfman-operator/tc-program_test.go +++ b/controllers/bpfman-operator/tc-program_test.go @@ -112,16 +112,20 @@ func TestTcProgramReconcile(t *testing.T) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithStatusSubresource(tc).WithRuntimeObjects(objs...).Build() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &TcProgramReconciler{ReconcilerCommon: rc} + r := &TcProgramReconciler{ClusterProgramReconciler: cpr} // Mock request to simulate Reconcile() being called on an event for a // watched resource . diff --git a/controllers/bpfman-operator/tcx-ns-program.go b/controllers/bpfman-operator/tcx-ns-program.go new file mode 100644 index 000000000..afd465af3 --- /dev/null +++ b/controllers/bpfman-operator/tcx-ns-program.go @@ -0,0 +1,133 @@ +/* +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. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanoperator + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman-operator/internal" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +type TcxNsProgramReconciler struct { + NamespaceProgramReconciler +} + +func (r *TcxNsProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList] { + return &r.NamespaceProgramReconciler.ReconcilerCommon +} + +func (r *TcxNsProgramReconciler) getFinalizer() string { + return internal.TcxNsProgramControllerFinalizer +} + +// SetupWithManager sets up the controller with the Manager. +func (r *TcxNsProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.TcxNsProgram{}). + // Watch BpfNsPrograms which are owned by TcxNsPrograms + Watches( + &bpfmaniov1alpha1.BpfNsProgram{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And( + statusChangedPredicateNamespace(), + internal.BpfNsProgramTypePredicate(internal.TcxString), + ), + ), + ). + Complete(r) +} + +//+kubebuilder:rbac:groups=bpfman.io,resources=tcxnsprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,resources=tcxnsprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,resources=tcxnsprograms/finalizers,verbs=update +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=tcxnsprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=tcxnsprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=tcxnsprograms/finalizers,verbs=update + +func (r *TcxNsProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Logger = ctrl.Log.WithName("tcx-ns") + r.Logger.Info("bpfman-operator enter: tcx-ns", "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + + tcxProgram := &bpfmaniov1alpha1.TcxNsProgram{} + if err := r.Get(ctx, req.NamespacedName, tcxProgram); err != nil { + // list all TcxNsProgram objects with + if errors.IsNotFound(err) { + bpfProgram := &bpfmaniov1alpha1.BpfNsProgram{} + if err := r.Get(ctx, req.NamespacedName, bpfProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.V(1).Info("BpfNsProgram not found stale reconcile, exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting BpfNsProgram Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + // Get owning TcxNsProgram object from ownerRef + ownerRef := metav1.GetControllerOf(bpfProgram) + if ownerRef == nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting BpfNsProgram Object owner") + } + + if err := r.Get(ctx, types.NamespacedName{Namespace: req.NamespacedName.Namespace, Name: ownerRef.Name}, tcxProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.Info("TcxNsProgram from ownerRef not found stale reconcile exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting TcxNsProgram Object from ownerRef", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + } else { + r.Logger.Error(err, "failed getting TcxNsProgram Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + return ctrl.Result{}, nil + } + } + + return reconcileBpfProgram(ctx, r, tcxProgram) +} + +func (r *TcxNsProgramReconciler) updateStatus( + ctx context.Context, + namespace string, + name string, + cond bpfmaniov1alpha1.ProgramConditionType, + message string, +) (ctrl.Result, error) { + // Sometimes we end up with a stale TcxNsProgram due to races, do this + // get to ensure we're up to date before attempting a finalizer removal. + prog := &bpfmaniov1alpha1.TcxNsProgram{} + if err := r.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, prog); err != nil { + r.Logger.V(1).Info("failed to get fresh TcxNsProgram object...requeuing") + return ctrl.Result{Requeue: true, RequeueAfter: retryDurationOperator}, nil + } + + return r.updateCondition(ctx, prog, &prog.Status.Conditions, cond, message) +} diff --git a/controllers/bpfman-operator/tcx-ns-program_test.go b/controllers/bpfman-operator/tcx-ns-program_test.go new file mode 100644 index 000000000..440e3c439 --- /dev/null +++ b/controllers/bpfman-operator/tcx-ns-program_test.go @@ -0,0 +1,172 @@ +/* +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 bpfmanoperator + +import ( + "context" + "fmt" + "testing" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestTcxNsProgramReconcile(t *testing.T) { + var ( + name = "fakeTcProgram" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + direction = "ingress" + fakeNode = testutils.NewNode("fake-control-plane") + fakeInt = "eth0" + ctx = context.TODO() + bpfProgName = fmt.Sprintf("%s-%s", name, fakeNode.Name) + ) + + // A TcxNsProgram object with metadata and spec. + tcx := &bpfmaniov1alpha1.TcxNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: bpfmaniov1alpha1.TcxNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + TcxNsProgramInfo: bpfmaniov1alpha1.TcxNsProgramInfo{ + + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &[]string{fakeInt}, + }, + Priority: 0, + Direction: direction, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + }, + }, + } + + // The expected accompanying BpfNsProgram object + expectedBpfProg := &bpfmaniov1alpha1.BpfNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: bpfProgName, + OwnerReferences: []metav1.OwnerReference{ + { + Name: tcx.Name, + Controller: &[]bool{true}[0], + }, + }, + Labels: map[string]string{internal.BpfProgramOwner: tcx.Name, internal.K8sHostLabel: fakeNode.Name}, + Finalizers: []string{internal.TcxNsProgramControllerFinalizer}, + }, + Spec: bpfmaniov1alpha1.BpfProgramSpec{ + Type: "tcx", + }, + Status: bpfmaniov1alpha1.BpfProgramStatus{ + Conditions: []metav1.Condition{bpfmaniov1alpha1.BpfProgCondLoaded.Condition()}, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, tcx, expectedBpfProg} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, tcx) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(tcx).WithRuntimeObjects(objs...).Build() + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + } + + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &TcxNsProgramReconciler{NamespaceProgramReconciler: npr} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + }, + } + + // First reconcile should add the finalzier to the tcProgram object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: tcx.Name, Namespace: tcx.Namespace}, tcx) + require.NoError(t, err) + + // Check the bpfman-operator finalizer was successfully added + require.Contains(t, tcx.GetFinalizers(), internal.BpfmanOperatorFinalizer) + + // Second reconcile should check BpfNsProgram Status and write Success condition to tcProgram Status + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: tcx.Name, Namespace: tcx.Namespace}, tcx) + require.NoError(t, err) + + require.Equal(t, tcx.Status.Conditions[0].Type, string(bpfmaniov1alpha1.ProgramReconcileSuccess)) + +} diff --git a/controllers/bpfman-operator/tcx-program.go b/controllers/bpfman-operator/tcx-program.go index a13dc4f9d..57303f8d1 100644 --- a/controllers/bpfman-operator/tcx-program.go +++ b/controllers/bpfman-operator/tcx-program.go @@ -11,6 +11,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanoperator import ( @@ -31,11 +33,11 @@ import ( ) type TcxProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler } -func (r *TcxProgramReconciler) getRecCommon() *ReconcilerCommon { - return &r.ReconcilerCommon +func (r *TcxProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] { + return &r.ClusterProgramReconciler.ReconcilerCommon } func (r *TcxProgramReconciler) getFinalizer() string { @@ -50,7 +52,7 @@ func (r *TcxProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &bpfmaniov1alpha1.BpfProgram{}, &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.TcxString))), + builder.WithPredicates(predicate.And(statusChangedPredicateCluster(), internal.BpfProgramTypePredicate(internal.TcxString))), ). Complete(r) } @@ -101,7 +103,7 @@ func (r *TcxProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) return reconcileBpfProgram(ctx, r, tcxProgram) } -func (r *TcxProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +func (r *TcxProgramReconciler) updateStatus(ctx context.Context, _namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { // Sometimes we end up with a stale TcxProgram due to races, do this // get to ensure we're up to date before attempting a finalizer removal. prog := &bpfmaniov1alpha1.TcxProgram{} diff --git a/controllers/bpfman-operator/tcx-program_test.go b/controllers/bpfman-operator/tcx-program_test.go index 5623e995f..1a042b320 100644 --- a/controllers/bpfman-operator/tcx-program_test.go +++ b/controllers/bpfman-operator/tcx-program_test.go @@ -108,16 +108,20 @@ func TestTcxProgramReconcile(t *testing.T) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithStatusSubresource(tcx).WithRuntimeObjects(objs...).Build() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &TcxProgramReconciler{ReconcilerCommon: rc} + r := &TcxProgramReconciler{ClusterProgramReconciler: cpr} // Mock request to simulate Reconcile() being called on an event for a // watched resource . diff --git a/controllers/bpfman-operator/tracepoint-program.go b/controllers/bpfman-operator/tracepoint-program.go index 758d509b0..908529b1f 100644 --- a/controllers/bpfman-operator/tracepoint-program.go +++ b/controllers/bpfman-operator/tracepoint-program.go @@ -11,6 +11,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanoperator import ( @@ -35,11 +37,11 @@ import ( //+kubebuilder:rbac:groups=bpfman.io,resources=tracepointprograms/finalizers,verbs=update type TracepointProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler } -func (r *TracepointProgramReconciler) getRecCommon() *ReconcilerCommon { - return &r.ReconcilerCommon +func (r *TracepointProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] { + return &r.ClusterProgramReconciler.ReconcilerCommon } func (r *TracepointProgramReconciler) getFinalizer() string { @@ -54,7 +56,7 @@ func (r *TracepointProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &bpfmaniov1alpha1.BpfProgram{}, &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.Tracepoint.String()))), + builder.WithPredicates(predicate.And(statusChangedPredicateCluster(), internal.BpfProgramTypePredicate(internal.Tracepoint.String()))), ). Complete(r) } @@ -101,7 +103,7 @@ func (r *TracepointProgramReconciler) Reconcile(ctx context.Context, req ctrl.Re return reconcileBpfProgram(ctx, r, tracepointProgram) } -func (r *TracepointProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +func (r *TracepointProgramReconciler) updateStatus(ctx context.Context, _namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { // Sometimes we end up with a stale TracepointProgram due to races, do this // get to ensure we're up to date before attempting a finalizer removal. prog := &bpfmaniov1alpha1.TracepointProgram{} diff --git a/controllers/bpfman-operator/tracepoint-program_test.go b/controllers/bpfman-operator/tracepoint-program_test.go index 8cac0a367..7b3e2756d 100644 --- a/controllers/bpfman-operator/tracepoint-program_test.go +++ b/controllers/bpfman-operator/tracepoint-program_test.go @@ -102,16 +102,20 @@ func TestTracepointProgramReconcile(t *testing.T) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithStatusSubresource(Tracepoint).WithRuntimeObjects(objs...).Build() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a TracepointProgram object with the scheme and fake client. - r := &TracepointProgramReconciler{ReconcilerCommon: rc} + r := &TracepointProgramReconciler{ClusterProgramReconciler: cpr} // Mock request to simulate Reconcile() being called on an event for a // watched resource . diff --git a/controllers/bpfman-operator/uprobe-ns-program.go b/controllers/bpfman-operator/uprobe-ns-program.go new file mode 100644 index 000000000..2d3c232a2 --- /dev/null +++ b/controllers/bpfman-operator/uprobe-ns-program.go @@ -0,0 +1,133 @@ +/* +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. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanoperator + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman-operator/internal" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=uprobensprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,resources=uprobensprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,resources=uprobensprograms/finalizers,verbs=update +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=uprobensprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=uprobensprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=uprobensprograms/finalizers,verbs=update + +type UprobeNsProgramReconciler struct { + NamespaceProgramReconciler +} + +func (r *UprobeNsProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList] { + return &r.NamespaceProgramReconciler.ReconcilerCommon +} + +func (r *UprobeNsProgramReconciler) getFinalizer() string { + return internal.UprobeNsProgramControllerFinalizer +} + +// SetupWithManager sets up the controller with the Manager. +func (r *UprobeNsProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.UprobeNsProgram{}). + // Watch BpfNsPrograms which are owned by UprobeNsPrograms + Watches( + &bpfmaniov1alpha1.BpfNsProgram{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates( + predicate.And(statusChangedPredicateNamespace(), + internal.BpfNsProgramTypePredicate(internal.UprobeString)), + ), + ). + Complete(r) +} + +func (r *UprobeNsProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Logger = ctrl.Log.WithName("uprobe-ns") + r.Logger.Info("bpfman-operator enter: uprobe-ns", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + + uprobeProgram := &bpfmaniov1alpha1.UprobeNsProgram{} + if err := r.Get(ctx, req.NamespacedName, uprobeProgram); err != nil { + // Reconcile was triggered by BpfNsProgram event, get parent UprobeNsProgram Object. + if errors.IsNotFound(err) { + bpfProgram := &bpfmaniov1alpha1.BpfNsProgram{} + if err := r.Get(ctx, req.NamespacedName, bpfProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.V(1).Info("BpfNsProgram not found stale reconcile, exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting BpfNsProgram Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + // Get owning UprobeNsProgram object from ownerRef + ownerRef := metav1.GetControllerOf(bpfProgram) + if ownerRef == nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting BpfNsProgram Object owner") + } + + if err := r.Get(ctx, types.NamespacedName{Namespace: req.NamespacedName.Namespace, Name: ownerRef.Name}, uprobeProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.Info("Uprobe Program from ownerRef not found stale reconcile exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting UprobeNsProgram Object from ownerRef", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + } else { + r.Logger.Error(err, "failed getting UprobeNsProgram Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + return ctrl.Result{}, nil + } + } + + return reconcileBpfProgram(ctx, r, uprobeProgram) +} + +func (r *UprobeNsProgramReconciler) updateStatus( + ctx context.Context, + namespace string, + name string, + cond bpfmaniov1alpha1.ProgramConditionType, + message string, +) (ctrl.Result, error) { + // Sometimes we end up with a stale UprobeNsProgram due to races, do this + // get to ensure we're up to date before attempting a status update. + prog := &bpfmaniov1alpha1.UprobeNsProgram{} + if err := r.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, prog); err != nil { + r.Logger.V(1).Info("failed to get fresh UprobeNsProgram object...requeuing") + return ctrl.Result{Requeue: true, RequeueAfter: retryDurationOperator}, nil + } + + return r.updateCondition(ctx, prog, &prog.Status.Conditions, cond, message) +} diff --git a/controllers/bpfman-operator/uprobe-ns-program_test.go b/controllers/bpfman-operator/uprobe-ns-program_test.go new file mode 100644 index 000000000..e99389125 --- /dev/null +++ b/controllers/bpfman-operator/uprobe-ns-program_test.go @@ -0,0 +1,171 @@ +/* +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 bpfmanoperator + +import ( + "context" + "fmt" + "testing" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + internal "github.com/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestUprobeNsProgramReconcile(t *testing.T) { + var ( + name = "fakeUprobeProgram" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + fakeNode = testutils.NewNode("fake-control-plane") + functionName = "malloc" + target = "libc" + offset = 0 + retprobe = false + ctx = context.TODO() + bpfProgName = fmt.Sprintf("%s-%s", name, fakeNode.Name) + ) + // A UprobeNsProgram object with metadata and spec. + Uprobe := &bpfmaniov1alpha1.UprobeNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: bpfmaniov1alpha1.UprobeNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + UprobeNsProgramInfo: bpfmaniov1alpha1.UprobeNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + FunctionName: functionName, + Target: target, + Offset: uint64(offset), + RetProbe: retprobe, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + }, + }, + } + + // The expected accompanying BpfNsProgram object + expectedBpfProg := &bpfmaniov1alpha1.BpfNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: bpfProgName, + OwnerReferences: []metav1.OwnerReference{ + { + Name: Uprobe.Name, + Controller: &[]bool{true}[0], + }, + }, + Labels: map[string]string{internal.BpfProgramOwner: Uprobe.Name, internal.K8sHostLabel: fakeNode.Name}, + Finalizers: []string{internal.UprobeNsProgramControllerFinalizer}, + }, + Spec: bpfmaniov1alpha1.BpfProgramSpec{ + Type: "uprobe", + }, + Status: bpfmaniov1alpha1.BpfProgramStatus{ + Conditions: []metav1.Condition{bpfmaniov1alpha1.BpfProgCondLoaded.Condition()}, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, Uprobe, expectedBpfProg} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, Uprobe) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(Uprobe).WithRuntimeObjects(objs...).Build() + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + } + + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a UprobeNsProgram object with the scheme and fake client. + r := &UprobeNsProgramReconciler{NamespaceProgramReconciler: npr} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + }, + } + + // First reconcile should add the finalzier to the UprobeNsProgram object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: Uprobe.Name, Namespace: Uprobe.Namespace}, Uprobe) + require.NoError(t, err) + + // Check the bpfman-operator finalizer was successfully added + require.Contains(t, Uprobe.GetFinalizers(), internal.BpfmanOperatorFinalizer) + + // Second reconcile should check BpfNsProgram Status and write Success condition to tcProgram Status + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: Uprobe.Name, Namespace: Uprobe.Namespace}, Uprobe) + require.NoError(t, err) + + require.Equal(t, Uprobe.Status.Conditions[0].Type, string(bpfmaniov1alpha1.ProgramReconcileSuccess)) + +} diff --git a/controllers/bpfman-operator/uprobe-program.go b/controllers/bpfman-operator/uprobe-program.go index dd02d3643..cdc4e571a 100644 --- a/controllers/bpfman-operator/uprobe-program.go +++ b/controllers/bpfman-operator/uprobe-program.go @@ -11,6 +11,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanoperator import ( @@ -35,11 +37,11 @@ import ( //+kubebuilder:rbac:groups=bpfman.io,resources=uprobeprograms/finalizers,verbs=update type UprobeProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler } -func (r *UprobeProgramReconciler) getRecCommon() *ReconcilerCommon { - return &r.ReconcilerCommon +func (r *UprobeProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] { + return &r.ClusterProgramReconciler.ReconcilerCommon } func (r *UprobeProgramReconciler) getFinalizer() string { @@ -54,7 +56,7 @@ func (r *UprobeProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &bpfmaniov1alpha1.BpfProgram{}, &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.UprobeString))), + builder.WithPredicates(predicate.And(statusChangedPredicateCluster(), internal.BpfProgramTypePredicate(internal.UprobeString))), ). Complete(r) } @@ -101,7 +103,7 @@ func (r *UprobeProgramReconciler) Reconcile(ctx context.Context, req ctrl.Reques return reconcileBpfProgram(ctx, r, uprobeProgram) } -func (r *UprobeProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +func (r *UprobeProgramReconciler) updateStatus(ctx context.Context, _namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { // Sometimes we end up with a stale UprobeProgram due to races, do this // get to ensure we're up to date before attempting a status update. prog := &bpfmaniov1alpha1.UprobeProgram{} diff --git a/controllers/bpfman-operator/uprobe-program_test.go b/controllers/bpfman-operator/uprobe-program_test.go index d591165fd..cb13972e7 100644 --- a/controllers/bpfman-operator/uprobe-program_test.go +++ b/controllers/bpfman-operator/uprobe-program_test.go @@ -108,16 +108,20 @@ func TestUprobeProgramReconcile(t *testing.T) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithStatusSubresource(Uprobe).WithRuntimeObjects(objs...).Build() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a UprobeProgram object with the scheme and fake client. - r := &UprobeProgramReconciler{ReconcilerCommon: rc} + r := &UprobeProgramReconciler{ClusterProgramReconciler: cpr} // Mock request to simulate Reconcile() being called on an event for a // watched resource . diff --git a/controllers/bpfman-operator/xdp-ns-program.go b/controllers/bpfman-operator/xdp-ns-program.go new file mode 100644 index 000000000..16b0a0d26 --- /dev/null +++ b/controllers/bpfman-operator/xdp-ns-program.go @@ -0,0 +1,135 @@ +/* +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. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanoperator + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman-operator/internal" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=xdpnsprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,resources=xdpnsprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,resources=xdpnsprograms/finalizers,verbs=update +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=xdpnsprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=xdpnsprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=xdpnsprograms/finalizers,verbs=update + +type XdpNsProgramReconciler struct { + NamespaceProgramReconciler +} + +func (r *XdpNsProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList] { + return &r.NamespaceProgramReconciler.ReconcilerCommon +} + +func (r *XdpNsProgramReconciler) getFinalizer() string { + return internal.XdpNsProgramControllerFinalizer +} + +// SetupWithManager sets up the controller with the Manager. +func (r *XdpNsProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.XdpNsProgram{}). + // Watch bpfPrograms which are owned by XdpNsPrograms + Watches( + &bpfmaniov1alpha1.BpfNsProgram{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And( + statusChangedPredicateNamespace(), + internal.BpfNsProgramTypePredicate(internal.Xdp.String())), + ), + ). + Complete(r) +} + +func (r *XdpNsProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Logger = ctrl.Log.WithName("xdp-ns") + r.Logger.Info("bpfman-operator enter: xdp-ns", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + + xdpNsProgram := &bpfmaniov1alpha1.XdpNsProgram{} + if err := r.Get(ctx, req.NamespacedName, xdpNsProgram); err != nil { + // list all XdpNsProgram objects with + if errors.IsNotFound(err) { + // TODO(astoycos) we could simplify this logic by making the name of the + // generated bpfProgram object a bit more deterministic + bpfProgram := &bpfmaniov1alpha1.BpfNsProgram{} + if err := r.Get(ctx, req.NamespacedName, bpfProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.V(1).Info("bpfProgram not found stale reconcile, exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting bpfProgram Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + // Get owning XdpNsProgram object from ownerRef + ownerRef := metav1.GetControllerOf(bpfProgram) + if ownerRef == nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfProgram Object owner") + } + + if err := r.Get(ctx, types.NamespacedName{Namespace: req.NamespacedName.Namespace, Name: ownerRef.Name}, xdpNsProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.Info("xdpNsProgram from ownerRef not found stale reconcile exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting XdpNsProgram Object from ownerRef", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + } else { + r.Logger.Error(err, "failed getting XdpNsProgram Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + return ctrl.Result{}, nil + } + } + + return reconcileBpfProgram(ctx, r, xdpNsProgram) +} + +func (r *XdpNsProgramReconciler) updateStatus( + ctx context.Context, + namespace string, + name string, + cond bpfmaniov1alpha1.ProgramConditionType, + message string, +) (ctrl.Result, error) { + // Sometimes we end up with a stale XdpNsProgram due to races, do this + // get to ensure we're up to date before attempting a finalizer removal. + prog := &bpfmaniov1alpha1.XdpNsProgram{} + if err := r.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, prog); err != nil { + r.Logger.V(1).Error(err, "failed to get fresh XdpNsProgram object...requeuing") + return ctrl.Result{Requeue: true, RequeueAfter: retryDurationOperator}, nil + } + + return r.updateCondition(ctx, prog, &prog.Status.Conditions, cond, message) +} diff --git a/controllers/bpfman-operator/xdp-ns-program_test.go b/controllers/bpfman-operator/xdp-ns-program_test.go new file mode 100644 index 000000000..3f837cef2 --- /dev/null +++ b/controllers/bpfman-operator/xdp-ns-program_test.go @@ -0,0 +1,175 @@ +/* +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 bpfmanoperator + +import ( + "context" + "fmt" + "testing" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + internal "github.com/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestXdpNsProgramReconcile(t *testing.T) { + var ( + name = "fakeXdpNsProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + fakeNode = testutils.NewNode("fake-control-plane") + fakeInt = "eth0" + ctx = context.TODO() + bpfProgName = fmt.Sprintf("%s-%s", name, fakeNode.Name) + ) + // A XdpNsProgram object with metadata and spec. + Xdp := &bpfmaniov1alpha1.XdpNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: bpfmaniov1alpha1.XdpNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + XdpNsProgramInfo: bpfmaniov1alpha1.XdpNsProgramInfo{ + + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &[]string{fakeInt}, + }, + Priority: 0, + ProceedOn: []bpfmaniov1alpha1.XdpProceedOnValue{bpfmaniov1alpha1.XdpProceedOnValue("pass"), + bpfmaniov1alpha1.XdpProceedOnValue("dispatcher_return"), + }, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + }, + }, + } + + // The expected accompanying BpfNsProgram object + expectedBpfProg := &bpfmaniov1alpha1.BpfNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: bpfProgName, + Namespace: namespace, + OwnerReferences: []metav1.OwnerReference{ + { + Name: Xdp.Name, + Controller: &[]bool{true}[0], + }, + }, + Labels: map[string]string{internal.BpfProgramOwner: Xdp.Name, internal.K8sHostLabel: fakeNode.Name}, + Finalizers: []string{internal.TcProgramControllerFinalizer}, + }, + Spec: bpfmaniov1alpha1.BpfProgramSpec{ + Type: "xdp", + }, + Status: bpfmaniov1alpha1.BpfProgramStatus{ + Conditions: []metav1.Condition{bpfmaniov1alpha1.BpfProgCondLoaded.Condition()}, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, Xdp, expectedBpfProg} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, Xdp) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(Xdp).WithRuntimeObjects(objs...).Build() + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + } + + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &XdpNsProgramReconciler{NamespaceProgramReconciler: npr} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should add the finalzier to the XdpNsProgram object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: Xdp.Name, Namespace: Xdp.Namespace}, Xdp) + require.NoError(t, err) + + // Check the bpfman-operator finalizer was successfully added + require.Contains(t, Xdp.GetFinalizers(), internal.BpfmanOperatorFinalizer) + + // Second reconcile should check BpfNsProgram Status and write Success condition to tcProgram Status + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: Xdp.Name, Namespace: Xdp.Namespace}, Xdp) + require.NoError(t, err) + + require.Equal(t, Xdp.Status.Conditions[0].Type, string(bpfmaniov1alpha1.ProgramReconcileSuccess)) +} diff --git a/controllers/bpfman-operator/xdp-program.go b/controllers/bpfman-operator/xdp-program.go index c8603c588..f5493f373 100644 --- a/controllers/bpfman-operator/xdp-program.go +++ b/controllers/bpfman-operator/xdp-program.go @@ -11,6 +11,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanoperator import ( @@ -35,11 +37,11 @@ import ( //+kubebuilder:rbac:groups=bpfman.io,resources=xdpprograms/finalizers,verbs=update type XdpProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler } -func (r *XdpProgramReconciler) getRecCommon() *ReconcilerCommon { - return &r.ReconcilerCommon +func (r *XdpProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] { + return &r.ClusterProgramReconciler.ReconcilerCommon } func (r *XdpProgramReconciler) getFinalizer() string { @@ -54,7 +56,7 @@ func (r *XdpProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &bpfmaniov1alpha1.BpfProgram{}, &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.Xdp.String()))), + builder.WithPredicates(predicate.And(statusChangedPredicateCluster(), internal.BpfProgramTypePredicate(internal.Xdp.String()))), ). Complete(r) } @@ -103,7 +105,7 @@ func (r *XdpProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) return reconcileBpfProgram(ctx, r, xdpProgram) } -func (r *XdpProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +func (r *XdpProgramReconciler) updateStatus(ctx context.Context, _namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { // Sometimes we end up with a stale XdpProgram due to races, do this // get to ensure we're up to date before attempting a finalizer removal. prog := &bpfmaniov1alpha1.XdpProgram{} diff --git a/controllers/bpfman-operator/xdp-program_test.go b/controllers/bpfman-operator/xdp-program_test.go index 92223a008..53aaa5457 100644 --- a/controllers/bpfman-operator/xdp-program_test.go +++ b/controllers/bpfman-operator/xdp-program_test.go @@ -89,7 +89,7 @@ func TestXdpProgramReconcile(t *testing.T) { Finalizers: []string{internal.TcProgramControllerFinalizer}, }, Spec: bpfmaniov1alpha1.BpfProgramSpec{ - Type: "tc", + Type: "xdp", }, Status: bpfmaniov1alpha1.BpfProgramStatus{ Conditions: []metav1.Condition{bpfmaniov1alpha1.BpfProgCondLoaded.Condition()}, @@ -108,16 +108,20 @@ func TestXdpProgramReconcile(t *testing.T) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithStatusSubresource(Xdp).WithRuntimeObjects(objs...).Build() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &XdpProgramReconciler{ReconcilerCommon: rc} + r := &XdpProgramReconciler{ClusterProgramReconciler: cpr} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -157,5 +161,4 @@ func TestXdpProgramReconcile(t *testing.T) { require.NoError(t, err) require.Equal(t, Xdp.Status.Conditions[0].Type, string(bpfmaniov1alpha1.ProgramReconcileSuccess)) - } diff --git a/hack/namespace_scoped.sh b/hack/namespace_scoped.sh new file mode 100755 index 000000000..34554668b --- /dev/null +++ b/hack/namespace_scoped.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash + +set -o errexit + +# This script generates the contents for a kubeconfig file for a given ServiceAccount. + +# Default the control variables, but they can be overwritten by passing in environment variables. +CLUSTER_NAME=${CLUSTER_NAME:-bpfman-deployment} +NAMESPACE=${NAMESPACE:-acme} +SERVICE_ACCOUNT=${SERVICE_ACCOUNT:-test-account} +SECRET_NAME=${SECRET_NAME:-test-account-token} + +# If a file is passed in, try to initialize the control variables from data in the file. +if [ ! -z "$1" ] ; then + if [ -f $1 ]; then + # For each variable below, pull the `kind: Secret` object from the file, which are the contents + # between "kind: Secret" and a possible "---". Then pipe that output into a `grep` to find + # individual fields. Then us `awk` to pull put the value. + + # Get NAMESPACE + tmpVar=$(awk '/kind: Secret/,/---/' $1 | grep -m 1 " namespace:" | awk -F' ' '{print $2}') + if [ ! -z "$tmpVar" ] ; then + NAMESPACE=${tmpVar} + fi + + # Get SERVICE_ACCOUNT + tmpVar=$(awk '/kind: Secret/,/---/' $1 | grep -m 1 " kubernetes.io/service-account.name:" | awk -F' ' '{print $2}') + if [ ! -z "$tmpVar" ] ; then + SERVICE_ACCOUNT=${tmpVar} + fi + + # Get SECRET_NAME + tmpVar=$(awk '/kind: Secret/,/---/' $1 | grep -m 1 " name:" | awk -F' ' '{print $2}') + if [ ! -z "$tmpVar" ] ; then + SECRET_NAME=${tmpVar} + fi + fi +fi + +# Determine the server IP Address using `kubectl cluster-info`. +# The first line of the output looks like: +# "Kubernetes control plane is running at https://127.0.0.1:35841" +# The `grep` command gets the first instance of the "https" and returns that line. +# The 'awk' command pulls out the 7th word in the sentence, the "https:" part. +# The `sed` command strips off some color control characters that are include in the output. +server=$(kubectl cluster-info | grep -m 1 "https" | awk -F' ' '{print $7}' | sed 's/\x1b\[[0-9;]*m//g') + +# Get the secret data. +ca=$(kubectl --namespace="${NAMESPACE}" get secret/"${SECRET_NAME}" -o=jsonpath='{.data.ca\.crt}') +token=$(kubectl --namespace="${NAMESPACE}" get secret/"${SECRET_NAME}" -o=jsonpath='{.data.token}' | base64 --decode) + +# Generate a kubeconfig based on gathered data. +echo "apiVersion: v1 +kind: Config +clusters: + - name: ${CLUSTER_NAME} + cluster: + certificate-authority-data: ${ca} + server: ${server} +contexts: + - name: ${SERVICE_ACCOUNT}@${CLUSTER_NAME} + context: + cluster: ${CLUSTER_NAME} + namespace: ${NAMESPACE} + user: ${SERVICE_ACCOUNT} +users: + - name: ${SERVICE_ACCOUNT} + user: + token: ${token} +current-context: ${SERVICE_ACCOUNT}@${CLUSTER_NAME} +" diff --git a/hack/namespace_scoped.yaml b/hack/namespace_scoped.yaml new file mode 100644 index 000000000..3e48015ea --- /dev/null +++ b/hack/namespace_scoped.yaml @@ -0,0 +1,76 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: acme +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: test-account + namespace: acme +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: test-account + namespace: acme +rules: +- apiGroups: ['', 'extensions', 'apps'] + resources: ['*'] + verbs: ['*'] +- apiGroups: ['batch'] + resources: + - jobs + - cronjobs + verbs: ['*'] +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms/status + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + # resources: ['xdpnsprograms'] + resources: ['bpfnsapplications', 'tcnsprograms', 'tcxnsprograms', 'uprobensprograms', 'xdpnsprograms'] + verbs: + - create + - delete + - get + - list + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: test-account + namespace: acme +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: test-account +subjects: + - kind: ServiceAccount + name: test-account + namespace: acme +--- +apiVersion: v1 +kind: Secret +metadata: + name: test-account-token + namespace: acme + annotations: + kubernetes.io/service-account.name: test-account +type: kubernetes.io/service-account-token diff --git a/hack/nginx-deployment.yaml b/hack/nginx-deployment.yaml index 656ddc322..330fb2c14 100644 --- a/hack/nginx-deployment.yaml +++ b/hack/nginx-deployment.yaml @@ -2,6 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment + namespace: acme spec: selector: matchLabels: diff --git a/internal/constants.go b/internal/constants.go index 3c324dde8..8ccf208e4 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -19,43 +19,59 @@ package internal import "fmt" const ( - XdpProgramInterface = "bpfman.io.xdpprogramcontroller/interface" - XdpContainerPid = "bpfman.io.xdpprogramcontroller/containerpid" - XdpNoContainersOnNode = "bpfman.io.xdpprogramcontroller/nocontainersonnode" - TcProgramInterface = "bpfman.io.tcprogramcontroller/interface" - TcContainerPid = "bpfman.io.tcprogramcontroller/containerpid" - TcNoContainersOnNode = "bpfman.io.tcprogramcontroller/nocontainersonnode" - TcxProgramInterface = "bpfman.io.tcxprogramcontroller/interface" - TcxContainerPid = "bpfman.io.tcxprogramcontroller/containerpid" - TcxNoContainersOnNode = "bpfman.io.tcxprogramcontroller/nocontainersonnode" - TracepointProgramTracepoint = "bpfman.io.tracepointprogramcontroller/tracepoint" - KprobeProgramFunction = "bpfman.io.kprobeprogramcontroller/function" - UprobeProgramTarget = "bpfman.io.uprobeprogramcontroller/target" - UprobeContainerPid = "bpfman.io.uprobeprogramcontroller/containerpid" - UprobeNoContainersOnNode = "bpfman.io.uprobeprogramcontroller/nocontainersonnode" - FentryProgramFunction = "bpfman.io.fentryprogramcontroller/function" - FexitProgramFunction = "bpfman.io.fexitprogramcontroller/function" - K8sHostLabel = "kubernetes.io/hostname" - DiscoveredLabel = "bpfman.io/discoveredProgram" - IdAnnotation = "bpfman.io/ProgramId" - UuidMetadataKey = "bpfman.io/uuid" - ProgramNameKey = "bpfman.io/ProgramName" - BpfmanNs = "bpfman" - BpfmanOperatorName = "bpfman-operator" - BpfmanDsName = "bpfman-daemon" - BpfmanConfigName = "bpfman-config" - BpfmanCsiDriverName = "csi.bpfman.io" - BpfmanRestrictedSccName = "bpfman-restricted" - BpfmanContainerName = "bpfman" - BpfmanAgentContainerName = "bpfman-agent" - BpfmanDaemonManifestPath = "./config/bpfman-deployment/daemonset.yaml" - BpfmanCsiDriverPath = "./config/bpfman-deployment/csidriverinfo.yaml" - BpfmanRestrictedSCCPath = "./config/openshift/restricted-scc.yaml" - BpfmanMapFs = "/run/bpfman/fs/maps" - DefaultType = "tcp" - DefaultPath = "/run/bpfman-sock/bpfman.sock" - DefaultPort = 50051 - DefaultEnabled = true + FentryProgramFunction = "bpfman.io.fentryprogramcontroller/function" + FentryNoContainersOnNode = "bpfman.io.fentryprogramcontroller/nocontainersonnode" + FexitProgramFunction = "bpfman.io.fexitprogramcontroller/function" + FexitNoContainersOnNode = "bpfman.io.fexitprogramcontroller/nocontainersonnode" + KprobeProgramFunction = "bpfman.io.kprobeprogramcontroller/function" + KprobeNoContainersOnNode = "bpfman.io.kprobeprogramcontroller/nocontainersonnode" + TcProgramInterface = "bpfman.io.tcprogramcontroller/interface" + TcContainerPid = "bpfman.io.tcprogramcontroller/containerpid" + TcNoContainersOnNode = "bpfman.io.tcprogramcontroller/nocontainersonnode" + TcNsProgramInterface = "bpfman.io.tcnsprogramcontroller/interface" + TcNsContainerPid = "bpfman.io.tcnsprogramcontroller/containerpid" + TcNsNoContainersOnNode = "bpfman.io.tcnsprogramcontroller/nocontainersonnode" + TcxProgramInterface = "bpfman.io.tcxprogramcontroller/interface" + TcxContainerPid = "bpfman.io.tcxprogramcontroller/containerpid" + TcxNoContainersOnNode = "bpfman.io.tcxprogramcontroller/nocontainersonnode" + TcxNsProgramInterface = "bpfman.io.tcxnsprogramcontroller/interface" + TcxNsContainerPid = "bpfman.io.tcxnsprogramcontroller/containerpid" + TcxNsNoContainersOnNode = "bpfman.io.tcxnsprogramcontroller/nocontainersonnode" + TracepointProgramTracepoint = "bpfman.io.tracepointprogramcontroller/tracepoint" + TracepointNoContainersOnNode = "bpfman.io.tracepointprogramcontroller/nocontainersonnode" + UprobeProgramTarget = "bpfman.io.uprobeprogramcontroller/target" + UprobeContainerPid = "bpfman.io.uprobeprogramcontroller/containerpid" + UprobeNoContainersOnNode = "bpfman.io.uprobeprogramcontroller/nocontainersonnode" + UprobeNsProgramTarget = "bpfman.io.uprobensprogramcontroller/target" + UprobeNsContainerPid = "bpfman.io.uprobensprogramcontroller/containerpid" + UprobeNsNoContainersOnNode = "bpfman.io.uprobensprogramcontroller/nocontainersonnode" + XdpProgramInterface = "bpfman.io.xdpprogramcontroller/interface" + XdpContainerPid = "bpfman.io.xdpprogramcontroller/containerpid" + XdpNoContainersOnNode = "bpfman.io.xdpprogramcontroller/nocontainersonnode" + XdpNsProgramInterface = "bpfman.io.xdpnsprogramcontroller/interface" + XdpNsContainerPid = "bpfman.io.xdpnsprogramcontroller/containerpid" + XdpNsNoContainersOnNode = "bpfman.io.xdpnsprogramcontroller/nocontainersonnode" + K8sHostLabel = "kubernetes.io/hostname" + DiscoveredLabel = "bpfman.io/discoveredProgram" + IdAnnotation = "bpfman.io/ProgramId" + UuidMetadataKey = "bpfman.io/uuid" + ProgramNameKey = "bpfman.io/ProgramName" + BpfmanNs = "bpfman" + BpfmanOperatorName = "bpfman-operator" + BpfmanDsName = "bpfman-daemon" + BpfmanConfigName = "bpfman-config" + BpfmanCsiDriverName = "csi.bpfman.io" + BpfmanRestrictedSccName = "bpfman-restricted" + BpfmanContainerName = "bpfman" + BpfmanAgentContainerName = "bpfman-agent" + BpfmanDaemonManifestPath = "./config/bpfman-deployment/daemonset.yaml" + BpfmanCsiDriverPath = "./config/bpfman-deployment/csidriverinfo.yaml" + BpfmanRestrictedSCCPath = "./config/openshift/restricted-scc.yaml" + BpfmanMapFs = "/run/bpfman/fs/maps" + DefaultType = "tcp" + DefaultPath = "/run/bpfman-sock/bpfman.sock" + DefaultPort = 50051 + DefaultEnabled = true // BpfProgramOwner is the name of the object that owns the BpfProgram // object. In the case of a *Program, it will be the name of the *Program // object. In the case of a BpfApplication, it will be the name of the @@ -80,12 +96,21 @@ const ( // XdpProgramControllerFinalizer is the finalizer that holds an Xdp BpfProgram // object from deletion until cleanup can be performed. XdpProgramControllerFinalizer = "bpfman.io.xdpprogramcontroller/finalizer" + // XdpNsProgramControllerFinalizer is the finalizer that holds a Namespaced Xdp BpfProgram + // object from deletion until cleanup can be performed. + XdpNsProgramControllerFinalizer = "bpfman.io.xdpnsprogramcontroller/finalizer" // TcProgramControllerFinalizer is the finalizer that holds an Tc BpfProgram // object from deletion until cleanup can be performed. TcProgramControllerFinalizer = "bpfman.io.tcprogramcontroller/finalizer" + // TcNsProgramControllerFinalizer is the finalizer that holds a Namespaced Tc BpfProgram + // object from deletion until cleanup can be performed. + TcNsProgramControllerFinalizer = "bpfman.io.tcnsprogramcontroller/finalizer" // TcxProgramControllerFinalizer is the finalizer that holds an Tcx BpfProgram // object from deletion until cleanup can be performed. TcxProgramControllerFinalizer = "bpfman.io.tcxprogramcontroller/finalizer" + // TcxNsProgramControllerFinalizer is the finalizer that holds a Namespaced Tcx BpfProgram + // object from deletion until cleanup can be performed. + TcxNsProgramControllerFinalizer = "bpfman.io.tcxnsprogramcontroller/finalizer" // TracepointProgramControllerFinalizer is the finalizer that holds an Tracepoint // BpfProgram object from deletion until cleanup can be performed. TracepointProgramControllerFinalizer = "bpfman.io.tracepointprogramcontroller/finalizer" @@ -95,6 +120,9 @@ const ( // UprobeProgramControllerFinalizer is the finalizer that holds a Uprobe // BpfProgram object from deletion until cleanup can be performed. UprobeProgramControllerFinalizer = "bpfman.io.uprobeprogramcontroller/finalizer" + // UprobeNsProgramControllerFinalizer is the finalizer that holds a Namespaced Uprobe + // BpfProgram object from deletion until cleanup can be performed. + UprobeNsProgramControllerFinalizer = "bpfman.io.uprobensprogramcontroller/finalizer" // FentryProgramControllerFinalizer is the finalizer that holds a Fentry // BpfProgram object from deletion until cleanup can be performed. FentryProgramControllerFinalizer = "bpfman.io.fentryprogramcontroller/finalizer" @@ -103,6 +131,8 @@ const ( FexitProgramControllerFinalizer = "bpfman.io.fexitprogramcontroller/finalizer" // BpfApplicationFinalizer is the finalizer that holds a BpfApplication BpfApplicationControllerFinalizer = "bpfman.io.bpfapplicationcontroller/finalizer" + // BpfApplicationFinalizer is the finalizer that holds a BpfApplication + BpfNsApplicationControllerFinalizer = "bpfman.io.bpfnsapplicationcontroller/finalizer" ) // Must match the kernel's `bpf_prog_type` enum. diff --git a/internal/k8s.go b/internal/k8s.go index 2945b14f9..174178448 100644 --- a/internal/k8s.go +++ b/internal/k8s.go @@ -43,6 +43,24 @@ func BpfProgramTypePredicate(kind string) predicate.Funcs { } } +// Only reconcile if a bpfnsprogram has been created for the controller's program type. +func BpfNsProgramTypePredicate(kind string) predicate.Funcs { + return predicate.Funcs{ + GenericFunc: func(e event.GenericEvent) bool { + return e.Object.(*bpfmaniov1alpha1.BpfNsProgram).Spec.Type == kind + }, + CreateFunc: func(e event.CreateEvent) bool { + return e.Object.(*bpfmaniov1alpha1.BpfNsProgram).Spec.Type == kind + }, + UpdateFunc: func(e event.UpdateEvent) bool { + return e.ObjectNew.(*bpfmaniov1alpha1.BpfNsProgram).Spec.Type == kind + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return e.Object.(*bpfmaniov1alpha1.BpfNsProgram).Spec.Type == kind + }, + } +} + // Only reconcile if a bpfprogram has been created for a controller's node. func BpfProgramNodePredicate(nodeName string) predicate.Funcs { return predicate.Funcs{ diff --git a/pkg/client/apis/v1alpha1/bpfnsapplication.go b/pkg/client/apis/v1alpha1/bpfnsapplication.go new file mode 100644 index 000000000..ce117b0e7 --- /dev/null +++ b/pkg/client/apis/v1alpha1/bpfnsapplication.go @@ -0,0 +1,99 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// BpfNsApplicationLister helps list BpfNsApplications. +// All objects returned here must be treated as read-only. +type BpfNsApplicationLister interface { + // List lists all BpfNsApplications in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.BpfNsApplication, err error) + // BpfNsApplications returns an object that can list and get BpfNsApplications. + BpfNsApplications(namespace string) BpfNsApplicationNamespaceLister + BpfNsApplicationListerExpansion +} + +// bpfNsApplicationLister implements the BpfNsApplicationLister interface. +type bpfNsApplicationLister struct { + indexer cache.Indexer +} + +// NewBpfNsApplicationLister returns a new BpfNsApplicationLister. +func NewBpfNsApplicationLister(indexer cache.Indexer) BpfNsApplicationLister { + return &bpfNsApplicationLister{indexer: indexer} +} + +// List lists all BpfNsApplications in the indexer. +func (s *bpfNsApplicationLister) List(selector labels.Selector) (ret []*v1alpha1.BpfNsApplication, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.BpfNsApplication)) + }) + return ret, err +} + +// BpfNsApplications returns an object that can list and get BpfNsApplications. +func (s *bpfNsApplicationLister) BpfNsApplications(namespace string) BpfNsApplicationNamespaceLister { + return bpfNsApplicationNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// BpfNsApplicationNamespaceLister helps list and get BpfNsApplications. +// All objects returned here must be treated as read-only. +type BpfNsApplicationNamespaceLister interface { + // List lists all BpfNsApplications in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.BpfNsApplication, err error) + // Get retrieves the BpfNsApplication from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.BpfNsApplication, error) + BpfNsApplicationNamespaceListerExpansion +} + +// bpfNsApplicationNamespaceLister implements the BpfNsApplicationNamespaceLister +// interface. +type bpfNsApplicationNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all BpfNsApplications in the indexer for a given namespace. +func (s bpfNsApplicationNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.BpfNsApplication, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.BpfNsApplication)) + }) + return ret, err +} + +// Get retrieves the BpfNsApplication from the indexer for a given namespace and name. +func (s bpfNsApplicationNamespaceLister) Get(name string) (*v1alpha1.BpfNsApplication, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("bpfnsapplication"), name) + } + return obj.(*v1alpha1.BpfNsApplication), nil +} diff --git a/pkg/client/apis/v1alpha1/bpfnsprogram.go b/pkg/client/apis/v1alpha1/bpfnsprogram.go new file mode 100644 index 000000000..4846a24fb --- /dev/null +++ b/pkg/client/apis/v1alpha1/bpfnsprogram.go @@ -0,0 +1,99 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// BpfNsProgramLister helps list BpfNsPrograms. +// All objects returned here must be treated as read-only. +type BpfNsProgramLister interface { + // List lists all BpfNsPrograms in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.BpfNsProgram, err error) + // BpfNsPrograms returns an object that can list and get BpfNsPrograms. + BpfNsPrograms(namespace string) BpfNsProgramNamespaceLister + BpfNsProgramListerExpansion +} + +// bpfNsProgramLister implements the BpfNsProgramLister interface. +type bpfNsProgramLister struct { + indexer cache.Indexer +} + +// NewBpfNsProgramLister returns a new BpfNsProgramLister. +func NewBpfNsProgramLister(indexer cache.Indexer) BpfNsProgramLister { + return &bpfNsProgramLister{indexer: indexer} +} + +// List lists all BpfNsPrograms in the indexer. +func (s *bpfNsProgramLister) List(selector labels.Selector) (ret []*v1alpha1.BpfNsProgram, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.BpfNsProgram)) + }) + return ret, err +} + +// BpfNsPrograms returns an object that can list and get BpfNsPrograms. +func (s *bpfNsProgramLister) BpfNsPrograms(namespace string) BpfNsProgramNamespaceLister { + return bpfNsProgramNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// BpfNsProgramNamespaceLister helps list and get BpfNsPrograms. +// All objects returned here must be treated as read-only. +type BpfNsProgramNamespaceLister interface { + // List lists all BpfNsPrograms in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.BpfNsProgram, err error) + // Get retrieves the BpfNsProgram from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.BpfNsProgram, error) + BpfNsProgramNamespaceListerExpansion +} + +// bpfNsProgramNamespaceLister implements the BpfNsProgramNamespaceLister +// interface. +type bpfNsProgramNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all BpfNsPrograms in the indexer for a given namespace. +func (s bpfNsProgramNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.BpfNsProgram, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.BpfNsProgram)) + }) + return ret, err +} + +// Get retrieves the BpfNsProgram from the indexer for a given namespace and name. +func (s bpfNsProgramNamespaceLister) Get(name string) (*v1alpha1.BpfNsProgram, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("bpfnsprogram"), name) + } + return obj.(*v1alpha1.BpfNsProgram), nil +} diff --git a/pkg/client/apis/v1alpha1/expansion_generated.go b/pkg/client/apis/v1alpha1/expansion_generated.go index df6e451bd..491e889c5 100644 --- a/pkg/client/apis/v1alpha1/expansion_generated.go +++ b/pkg/client/apis/v1alpha1/expansion_generated.go @@ -22,6 +22,22 @@ package v1alpha1 // BpfApplicationLister. type BpfApplicationListerExpansion interface{} +// BpfNsApplicationListerExpansion allows custom methods to be added to +// BpfNsApplicationLister. +type BpfNsApplicationListerExpansion interface{} + +// BpfNsApplicationNamespaceListerExpansion allows custom methods to be added to +// BpfNsApplicationNamespaceLister. +type BpfNsApplicationNamespaceListerExpansion interface{} + +// BpfNsProgramListerExpansion allows custom methods to be added to +// BpfNsProgramLister. +type BpfNsProgramListerExpansion interface{} + +// BpfNsProgramNamespaceListerExpansion allows custom methods to be added to +// BpfNsProgramNamespaceLister. +type BpfNsProgramNamespaceListerExpansion interface{} + // BpfProgramListerExpansion allows custom methods to be added to // BpfProgramLister. type BpfProgramListerExpansion interface{} @@ -38,10 +54,26 @@ type FexitProgramListerExpansion interface{} // KprobeProgramLister. type KprobeProgramListerExpansion interface{} +// TcNsProgramListerExpansion allows custom methods to be added to +// TcNsProgramLister. +type TcNsProgramListerExpansion interface{} + +// TcNsProgramNamespaceListerExpansion allows custom methods to be added to +// TcNsProgramNamespaceLister. +type TcNsProgramNamespaceListerExpansion interface{} + // TcProgramListerExpansion allows custom methods to be added to // TcProgramLister. type TcProgramListerExpansion interface{} +// TcxNsProgramListerExpansion allows custom methods to be added to +// TcxNsProgramLister. +type TcxNsProgramListerExpansion interface{} + +// TcxNsProgramNamespaceListerExpansion allows custom methods to be added to +// TcxNsProgramNamespaceLister. +type TcxNsProgramNamespaceListerExpansion interface{} + // TcxProgramListerExpansion allows custom methods to be added to // TcxProgramLister. type TcxProgramListerExpansion interface{} @@ -50,10 +82,26 @@ type TcxProgramListerExpansion interface{} // TracepointProgramLister. type TracepointProgramListerExpansion interface{} +// UprobeNsProgramListerExpansion allows custom methods to be added to +// UprobeNsProgramLister. +type UprobeNsProgramListerExpansion interface{} + +// UprobeNsProgramNamespaceListerExpansion allows custom methods to be added to +// UprobeNsProgramNamespaceLister. +type UprobeNsProgramNamespaceListerExpansion interface{} + // UprobeProgramListerExpansion allows custom methods to be added to // UprobeProgramLister. type UprobeProgramListerExpansion interface{} +// XdpNsProgramListerExpansion allows custom methods to be added to +// XdpNsProgramLister. +type XdpNsProgramListerExpansion interface{} + +// XdpNsProgramNamespaceListerExpansion allows custom methods to be added to +// XdpNsProgramNamespaceLister. +type XdpNsProgramNamespaceListerExpansion interface{} + // XdpProgramListerExpansion allows custom methods to be added to // XdpProgramLister. type XdpProgramListerExpansion interface{} diff --git a/pkg/client/apis/v1alpha1/tcnsprogram.go b/pkg/client/apis/v1alpha1/tcnsprogram.go new file mode 100644 index 000000000..3a4632bf1 --- /dev/null +++ b/pkg/client/apis/v1alpha1/tcnsprogram.go @@ -0,0 +1,99 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// TcNsProgramLister helps list TcNsPrograms. +// All objects returned here must be treated as read-only. +type TcNsProgramLister interface { + // List lists all TcNsPrograms in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.TcNsProgram, err error) + // TcNsPrograms returns an object that can list and get TcNsPrograms. + TcNsPrograms(namespace string) TcNsProgramNamespaceLister + TcNsProgramListerExpansion +} + +// tcNsProgramLister implements the TcNsProgramLister interface. +type tcNsProgramLister struct { + indexer cache.Indexer +} + +// NewTcNsProgramLister returns a new TcNsProgramLister. +func NewTcNsProgramLister(indexer cache.Indexer) TcNsProgramLister { + return &tcNsProgramLister{indexer: indexer} +} + +// List lists all TcNsPrograms in the indexer. +func (s *tcNsProgramLister) List(selector labels.Selector) (ret []*v1alpha1.TcNsProgram, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.TcNsProgram)) + }) + return ret, err +} + +// TcNsPrograms returns an object that can list and get TcNsPrograms. +func (s *tcNsProgramLister) TcNsPrograms(namespace string) TcNsProgramNamespaceLister { + return tcNsProgramNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// TcNsProgramNamespaceLister helps list and get TcNsPrograms. +// All objects returned here must be treated as read-only. +type TcNsProgramNamespaceLister interface { + // List lists all TcNsPrograms in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.TcNsProgram, err error) + // Get retrieves the TcNsProgram from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.TcNsProgram, error) + TcNsProgramNamespaceListerExpansion +} + +// tcNsProgramNamespaceLister implements the TcNsProgramNamespaceLister +// interface. +type tcNsProgramNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all TcNsPrograms in the indexer for a given namespace. +func (s tcNsProgramNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.TcNsProgram, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.TcNsProgram)) + }) + return ret, err +} + +// Get retrieves the TcNsProgram from the indexer for a given namespace and name. +func (s tcNsProgramNamespaceLister) Get(name string) (*v1alpha1.TcNsProgram, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("tcnsprogram"), name) + } + return obj.(*v1alpha1.TcNsProgram), nil +} diff --git a/pkg/client/apis/v1alpha1/tcxnsprogram.go b/pkg/client/apis/v1alpha1/tcxnsprogram.go new file mode 100644 index 000000000..399b3403d --- /dev/null +++ b/pkg/client/apis/v1alpha1/tcxnsprogram.go @@ -0,0 +1,99 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// TcxNsProgramLister helps list TcxNsPrograms. +// All objects returned here must be treated as read-only. +type TcxNsProgramLister interface { + // List lists all TcxNsPrograms in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.TcxNsProgram, err error) + // TcxNsPrograms returns an object that can list and get TcxNsPrograms. + TcxNsPrograms(namespace string) TcxNsProgramNamespaceLister + TcxNsProgramListerExpansion +} + +// tcxNsProgramLister implements the TcxNsProgramLister interface. +type tcxNsProgramLister struct { + indexer cache.Indexer +} + +// NewTcxNsProgramLister returns a new TcxNsProgramLister. +func NewTcxNsProgramLister(indexer cache.Indexer) TcxNsProgramLister { + return &tcxNsProgramLister{indexer: indexer} +} + +// List lists all TcxNsPrograms in the indexer. +func (s *tcxNsProgramLister) List(selector labels.Selector) (ret []*v1alpha1.TcxNsProgram, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.TcxNsProgram)) + }) + return ret, err +} + +// TcxNsPrograms returns an object that can list and get TcxNsPrograms. +func (s *tcxNsProgramLister) TcxNsPrograms(namespace string) TcxNsProgramNamespaceLister { + return tcxNsProgramNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// TcxNsProgramNamespaceLister helps list and get TcxNsPrograms. +// All objects returned here must be treated as read-only. +type TcxNsProgramNamespaceLister interface { + // List lists all TcxNsPrograms in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.TcxNsProgram, err error) + // Get retrieves the TcxNsProgram from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.TcxNsProgram, error) + TcxNsProgramNamespaceListerExpansion +} + +// tcxNsProgramNamespaceLister implements the TcxNsProgramNamespaceLister +// interface. +type tcxNsProgramNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all TcxNsPrograms in the indexer for a given namespace. +func (s tcxNsProgramNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.TcxNsProgram, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.TcxNsProgram)) + }) + return ret, err +} + +// Get retrieves the TcxNsProgram from the indexer for a given namespace and name. +func (s tcxNsProgramNamespaceLister) Get(name string) (*v1alpha1.TcxNsProgram, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("tcxnsprogram"), name) + } + return obj.(*v1alpha1.TcxNsProgram), nil +} diff --git a/pkg/client/apis/v1alpha1/uprobensprogram.go b/pkg/client/apis/v1alpha1/uprobensprogram.go new file mode 100644 index 000000000..927061ec0 --- /dev/null +++ b/pkg/client/apis/v1alpha1/uprobensprogram.go @@ -0,0 +1,99 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// UprobeNsProgramLister helps list UprobeNsPrograms. +// All objects returned here must be treated as read-only. +type UprobeNsProgramLister interface { + // List lists all UprobeNsPrograms in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.UprobeNsProgram, err error) + // UprobeNsPrograms returns an object that can list and get UprobeNsPrograms. + UprobeNsPrograms(namespace string) UprobeNsProgramNamespaceLister + UprobeNsProgramListerExpansion +} + +// uprobeNsProgramLister implements the UprobeNsProgramLister interface. +type uprobeNsProgramLister struct { + indexer cache.Indexer +} + +// NewUprobeNsProgramLister returns a new UprobeNsProgramLister. +func NewUprobeNsProgramLister(indexer cache.Indexer) UprobeNsProgramLister { + return &uprobeNsProgramLister{indexer: indexer} +} + +// List lists all UprobeNsPrograms in the indexer. +func (s *uprobeNsProgramLister) List(selector labels.Selector) (ret []*v1alpha1.UprobeNsProgram, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.UprobeNsProgram)) + }) + return ret, err +} + +// UprobeNsPrograms returns an object that can list and get UprobeNsPrograms. +func (s *uprobeNsProgramLister) UprobeNsPrograms(namespace string) UprobeNsProgramNamespaceLister { + return uprobeNsProgramNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// UprobeNsProgramNamespaceLister helps list and get UprobeNsPrograms. +// All objects returned here must be treated as read-only. +type UprobeNsProgramNamespaceLister interface { + // List lists all UprobeNsPrograms in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.UprobeNsProgram, err error) + // Get retrieves the UprobeNsProgram from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.UprobeNsProgram, error) + UprobeNsProgramNamespaceListerExpansion +} + +// uprobeNsProgramNamespaceLister implements the UprobeNsProgramNamespaceLister +// interface. +type uprobeNsProgramNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all UprobeNsPrograms in the indexer for a given namespace. +func (s uprobeNsProgramNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.UprobeNsProgram, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.UprobeNsProgram)) + }) + return ret, err +} + +// Get retrieves the UprobeNsProgram from the indexer for a given namespace and name. +func (s uprobeNsProgramNamespaceLister) Get(name string) (*v1alpha1.UprobeNsProgram, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("uprobensprogram"), name) + } + return obj.(*v1alpha1.UprobeNsProgram), nil +} diff --git a/pkg/client/apis/v1alpha1/xdpnsprogram.go b/pkg/client/apis/v1alpha1/xdpnsprogram.go new file mode 100644 index 000000000..ba698cd14 --- /dev/null +++ b/pkg/client/apis/v1alpha1/xdpnsprogram.go @@ -0,0 +1,99 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// XdpNsProgramLister helps list XdpNsPrograms. +// All objects returned here must be treated as read-only. +type XdpNsProgramLister interface { + // List lists all XdpNsPrograms in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.XdpNsProgram, err error) + // XdpNsPrograms returns an object that can list and get XdpNsPrograms. + XdpNsPrograms(namespace string) XdpNsProgramNamespaceLister + XdpNsProgramListerExpansion +} + +// xdpNsProgramLister implements the XdpNsProgramLister interface. +type xdpNsProgramLister struct { + indexer cache.Indexer +} + +// NewXdpNsProgramLister returns a new XdpNsProgramLister. +func NewXdpNsProgramLister(indexer cache.Indexer) XdpNsProgramLister { + return &xdpNsProgramLister{indexer: indexer} +} + +// List lists all XdpNsPrograms in the indexer. +func (s *xdpNsProgramLister) List(selector labels.Selector) (ret []*v1alpha1.XdpNsProgram, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.XdpNsProgram)) + }) + return ret, err +} + +// XdpNsPrograms returns an object that can list and get XdpNsPrograms. +func (s *xdpNsProgramLister) XdpNsPrograms(namespace string) XdpNsProgramNamespaceLister { + return xdpNsProgramNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// XdpNsProgramNamespaceLister helps list and get XdpNsPrograms. +// All objects returned here must be treated as read-only. +type XdpNsProgramNamespaceLister interface { + // List lists all XdpNsPrograms in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.XdpNsProgram, err error) + // Get retrieves the XdpNsProgram from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.XdpNsProgram, error) + XdpNsProgramNamespaceListerExpansion +} + +// xdpNsProgramNamespaceLister implements the XdpNsProgramNamespaceLister +// interface. +type xdpNsProgramNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all XdpNsPrograms in the indexer for a given namespace. +func (s xdpNsProgramNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.XdpNsProgram, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.XdpNsProgram)) + }) + return ret, err +} + +// Get retrieves the XdpNsProgram from the indexer for a given namespace and name. +func (s xdpNsProgramNamespaceLister) Get(name string) (*v1alpha1.XdpNsProgram, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("xdpnsprogram"), name) + } + return obj.(*v1alpha1.XdpNsProgram), nil +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/apis_client.go b/pkg/client/clientset/typed/apis/v1alpha1/apis_client.go index ac2e4d3a1..7ec7b412d 100644 --- a/pkg/client/clientset/typed/apis/v1alpha1/apis_client.go +++ b/pkg/client/clientset/typed/apis/v1alpha1/apis_client.go @@ -29,14 +29,20 @@ import ( type BpfmanV1alpha1Interface interface { RESTClient() rest.Interface BpfApplicationsGetter + BpfNsApplicationsGetter + BpfNsProgramsGetter BpfProgramsGetter FentryProgramsGetter FexitProgramsGetter KprobeProgramsGetter + TcNsProgramsGetter TcProgramsGetter + TcxNsProgramsGetter TcxProgramsGetter TracepointProgramsGetter + UprobeNsProgramsGetter UprobeProgramsGetter + XdpNsProgramsGetter XdpProgramsGetter } @@ -49,6 +55,14 @@ func (c *BpfmanV1alpha1Client) BpfApplications() BpfApplicationInterface { return newBpfApplications(c) } +func (c *BpfmanV1alpha1Client) BpfNsApplications(namespace string) BpfNsApplicationInterface { + return newBpfNsApplications(c, namespace) +} + +func (c *BpfmanV1alpha1Client) BpfNsPrograms(namespace string) BpfNsProgramInterface { + return newBpfNsPrograms(c, namespace) +} + func (c *BpfmanV1alpha1Client) BpfPrograms() BpfProgramInterface { return newBpfPrograms(c) } @@ -65,10 +79,18 @@ func (c *BpfmanV1alpha1Client) KprobePrograms() KprobeProgramInterface { return newKprobePrograms(c) } +func (c *BpfmanV1alpha1Client) TcNsPrograms(namespace string) TcNsProgramInterface { + return newTcNsPrograms(c, namespace) +} + func (c *BpfmanV1alpha1Client) TcPrograms() TcProgramInterface { return newTcPrograms(c) } +func (c *BpfmanV1alpha1Client) TcxNsPrograms(namespace string) TcxNsProgramInterface { + return newTcxNsPrograms(c, namespace) +} + func (c *BpfmanV1alpha1Client) TcxPrograms() TcxProgramInterface { return newTcxPrograms(c) } @@ -77,10 +99,18 @@ func (c *BpfmanV1alpha1Client) TracepointPrograms() TracepointProgramInterface { return newTracepointPrograms(c) } +func (c *BpfmanV1alpha1Client) UprobeNsPrograms(namespace string) UprobeNsProgramInterface { + return newUprobeNsPrograms(c, namespace) +} + func (c *BpfmanV1alpha1Client) UprobePrograms() UprobeProgramInterface { return newUprobePrograms(c) } +func (c *BpfmanV1alpha1Client) XdpNsPrograms(namespace string) XdpNsProgramInterface { + return newXdpNsPrograms(c, namespace) +} + func (c *BpfmanV1alpha1Client) XdpPrograms() XdpProgramInterface { return newXdpPrograms(c) } diff --git a/pkg/client/clientset/typed/apis/v1alpha1/bpfnsapplication.go b/pkg/client/clientset/typed/apis/v1alpha1/bpfnsapplication.go new file mode 100644 index 000000000..ed96b8cad --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/bpfnsapplication.go @@ -0,0 +1,195 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + scheme "github.com/bpfman/bpfman-operator/pkg/client/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// BpfNsApplicationsGetter has a method to return a BpfNsApplicationInterface. +// A group's client should implement this interface. +type BpfNsApplicationsGetter interface { + BpfNsApplications(namespace string) BpfNsApplicationInterface +} + +// BpfNsApplicationInterface has methods to work with BpfNsApplication resources. +type BpfNsApplicationInterface interface { + Create(ctx context.Context, bpfNsApplication *v1alpha1.BpfNsApplication, opts v1.CreateOptions) (*v1alpha1.BpfNsApplication, error) + Update(ctx context.Context, bpfNsApplication *v1alpha1.BpfNsApplication, opts v1.UpdateOptions) (*v1alpha1.BpfNsApplication, error) + UpdateStatus(ctx context.Context, bpfNsApplication *v1alpha1.BpfNsApplication, opts v1.UpdateOptions) (*v1alpha1.BpfNsApplication, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.BpfNsApplication, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.BpfNsApplicationList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.BpfNsApplication, err error) + BpfNsApplicationExpansion +} + +// bpfNsApplications implements BpfNsApplicationInterface +type bpfNsApplications struct { + client rest.Interface + ns string +} + +// newBpfNsApplications returns a BpfNsApplications +func newBpfNsApplications(c *BpfmanV1alpha1Client, namespace string) *bpfNsApplications { + return &bpfNsApplications{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the bpfNsApplication, and returns the corresponding bpfNsApplication object, and an error if there is any. +func (c *bpfNsApplications) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.BpfNsApplication, err error) { + result = &v1alpha1.BpfNsApplication{} + err = c.client.Get(). + Namespace(c.ns). + Resource("bpfnsapplications"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of BpfNsApplications that match those selectors. +func (c *bpfNsApplications) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.BpfNsApplicationList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.BpfNsApplicationList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("bpfnsapplications"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested bpfNsApplications. +func (c *bpfNsApplications) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("bpfnsapplications"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a bpfNsApplication and creates it. Returns the server's representation of the bpfNsApplication, and an error, if there is any. +func (c *bpfNsApplications) Create(ctx context.Context, bpfNsApplication *v1alpha1.BpfNsApplication, opts v1.CreateOptions) (result *v1alpha1.BpfNsApplication, err error) { + result = &v1alpha1.BpfNsApplication{} + err = c.client.Post(). + Namespace(c.ns). + Resource("bpfnsapplications"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(bpfNsApplication). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a bpfNsApplication and updates it. Returns the server's representation of the bpfNsApplication, and an error, if there is any. +func (c *bpfNsApplications) Update(ctx context.Context, bpfNsApplication *v1alpha1.BpfNsApplication, opts v1.UpdateOptions) (result *v1alpha1.BpfNsApplication, err error) { + result = &v1alpha1.BpfNsApplication{} + err = c.client.Put(). + Namespace(c.ns). + Resource("bpfnsapplications"). + Name(bpfNsApplication.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(bpfNsApplication). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *bpfNsApplications) UpdateStatus(ctx context.Context, bpfNsApplication *v1alpha1.BpfNsApplication, opts v1.UpdateOptions) (result *v1alpha1.BpfNsApplication, err error) { + result = &v1alpha1.BpfNsApplication{} + err = c.client.Put(). + Namespace(c.ns). + Resource("bpfnsapplications"). + Name(bpfNsApplication.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(bpfNsApplication). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the bpfNsApplication and deletes it. Returns an error if one occurs. +func (c *bpfNsApplications) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("bpfnsapplications"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *bpfNsApplications) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("bpfnsapplications"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched bpfNsApplication. +func (c *bpfNsApplications) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.BpfNsApplication, err error) { + result = &v1alpha1.BpfNsApplication{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("bpfnsapplications"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/bpfnsprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/bpfnsprogram.go new file mode 100644 index 000000000..b71f972a0 --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/bpfnsprogram.go @@ -0,0 +1,195 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + scheme "github.com/bpfman/bpfman-operator/pkg/client/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// BpfNsProgramsGetter has a method to return a BpfNsProgramInterface. +// A group's client should implement this interface. +type BpfNsProgramsGetter interface { + BpfNsPrograms(namespace string) BpfNsProgramInterface +} + +// BpfNsProgramInterface has methods to work with BpfNsProgram resources. +type BpfNsProgramInterface interface { + Create(ctx context.Context, bpfNsProgram *v1alpha1.BpfNsProgram, opts v1.CreateOptions) (*v1alpha1.BpfNsProgram, error) + Update(ctx context.Context, bpfNsProgram *v1alpha1.BpfNsProgram, opts v1.UpdateOptions) (*v1alpha1.BpfNsProgram, error) + UpdateStatus(ctx context.Context, bpfNsProgram *v1alpha1.BpfNsProgram, opts v1.UpdateOptions) (*v1alpha1.BpfNsProgram, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.BpfNsProgram, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.BpfNsProgramList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.BpfNsProgram, err error) + BpfNsProgramExpansion +} + +// bpfNsPrograms implements BpfNsProgramInterface +type bpfNsPrograms struct { + client rest.Interface + ns string +} + +// newBpfNsPrograms returns a BpfNsPrograms +func newBpfNsPrograms(c *BpfmanV1alpha1Client, namespace string) *bpfNsPrograms { + return &bpfNsPrograms{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the bpfNsProgram, and returns the corresponding bpfNsProgram object, and an error if there is any. +func (c *bpfNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.BpfNsProgram, err error) { + result = &v1alpha1.BpfNsProgram{} + err = c.client.Get(). + Namespace(c.ns). + Resource("bpfnsprograms"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of BpfNsPrograms that match those selectors. +func (c *bpfNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.BpfNsProgramList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.BpfNsProgramList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("bpfnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested bpfNsPrograms. +func (c *bpfNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("bpfnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a bpfNsProgram and creates it. Returns the server's representation of the bpfNsProgram, and an error, if there is any. +func (c *bpfNsPrograms) Create(ctx context.Context, bpfNsProgram *v1alpha1.BpfNsProgram, opts v1.CreateOptions) (result *v1alpha1.BpfNsProgram, err error) { + result = &v1alpha1.BpfNsProgram{} + err = c.client.Post(). + Namespace(c.ns). + Resource("bpfnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(bpfNsProgram). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a bpfNsProgram and updates it. Returns the server's representation of the bpfNsProgram, and an error, if there is any. +func (c *bpfNsPrograms) Update(ctx context.Context, bpfNsProgram *v1alpha1.BpfNsProgram, opts v1.UpdateOptions) (result *v1alpha1.BpfNsProgram, err error) { + result = &v1alpha1.BpfNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("bpfnsprograms"). + Name(bpfNsProgram.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(bpfNsProgram). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *bpfNsPrograms) UpdateStatus(ctx context.Context, bpfNsProgram *v1alpha1.BpfNsProgram, opts v1.UpdateOptions) (result *v1alpha1.BpfNsProgram, err error) { + result = &v1alpha1.BpfNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("bpfnsprograms"). + Name(bpfNsProgram.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(bpfNsProgram). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the bpfNsProgram and deletes it. Returns an error if one occurs. +func (c *bpfNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("bpfnsprograms"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *bpfNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("bpfnsprograms"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched bpfNsProgram. +func (c *bpfNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.BpfNsProgram, err error) { + result = &v1alpha1.BpfNsProgram{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("bpfnsprograms"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_apis_client.go b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_apis_client.go index b83fbca87..9e7d2f76c 100644 --- a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_apis_client.go +++ b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_apis_client.go @@ -32,6 +32,14 @@ func (c *FakeBpfmanV1alpha1) BpfApplications() v1alpha1.BpfApplicationInterface return &FakeBpfApplications{c} } +func (c *FakeBpfmanV1alpha1) BpfNsApplications(namespace string) v1alpha1.BpfNsApplicationInterface { + return &FakeBpfNsApplications{c, namespace} +} + +func (c *FakeBpfmanV1alpha1) BpfNsPrograms(namespace string) v1alpha1.BpfNsProgramInterface { + return &FakeBpfNsPrograms{c, namespace} +} + func (c *FakeBpfmanV1alpha1) BpfPrograms() v1alpha1.BpfProgramInterface { return &FakeBpfPrograms{c} } @@ -48,10 +56,18 @@ func (c *FakeBpfmanV1alpha1) KprobePrograms() v1alpha1.KprobeProgramInterface { return &FakeKprobePrograms{c} } +func (c *FakeBpfmanV1alpha1) TcNsPrograms(namespace string) v1alpha1.TcNsProgramInterface { + return &FakeTcNsPrograms{c, namespace} +} + func (c *FakeBpfmanV1alpha1) TcPrograms() v1alpha1.TcProgramInterface { return &FakeTcPrograms{c} } +func (c *FakeBpfmanV1alpha1) TcxNsPrograms(namespace string) v1alpha1.TcxNsProgramInterface { + return &FakeTcxNsPrograms{c, namespace} +} + func (c *FakeBpfmanV1alpha1) TcxPrograms() v1alpha1.TcxProgramInterface { return &FakeTcxPrograms{c} } @@ -60,10 +76,18 @@ func (c *FakeBpfmanV1alpha1) TracepointPrograms() v1alpha1.TracepointProgramInte return &FakeTracepointPrograms{c} } +func (c *FakeBpfmanV1alpha1) UprobeNsPrograms(namespace string) v1alpha1.UprobeNsProgramInterface { + return &FakeUprobeNsPrograms{c, namespace} +} + func (c *FakeBpfmanV1alpha1) UprobePrograms() v1alpha1.UprobeProgramInterface { return &FakeUprobePrograms{c} } +func (c *FakeBpfmanV1alpha1) XdpNsPrograms(namespace string) v1alpha1.XdpNsProgramInterface { + return &FakeXdpNsPrograms{c, namespace} +} + func (c *FakeBpfmanV1alpha1) XdpPrograms() v1alpha1.XdpProgramInterface { return &FakeXdpPrograms{c} } diff --git a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_bpfnsapplication.go b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_bpfnsapplication.go new file mode 100644 index 000000000..614874b91 --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_bpfnsapplication.go @@ -0,0 +1,141 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeBpfNsApplications implements BpfNsApplicationInterface +type FakeBpfNsApplications struct { + Fake *FakeBpfmanV1alpha1 + ns string +} + +var bpfnsapplicationsResource = v1alpha1.SchemeGroupVersion.WithResource("bpfnsapplications") + +var bpfnsapplicationsKind = v1alpha1.SchemeGroupVersion.WithKind("BpfNsApplication") + +// Get takes name of the bpfNsApplication, and returns the corresponding bpfNsApplication object, and an error if there is any. +func (c *FakeBpfNsApplications) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.BpfNsApplication, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(bpfnsapplicationsResource, c.ns, name), &v1alpha1.BpfNsApplication{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsApplication), err +} + +// List takes label and field selectors, and returns the list of BpfNsApplications that match those selectors. +func (c *FakeBpfNsApplications) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.BpfNsApplicationList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(bpfnsapplicationsResource, bpfnsapplicationsKind, c.ns, opts), &v1alpha1.BpfNsApplicationList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.BpfNsApplicationList{ListMeta: obj.(*v1alpha1.BpfNsApplicationList).ListMeta} + for _, item := range obj.(*v1alpha1.BpfNsApplicationList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested bpfNsApplications. +func (c *FakeBpfNsApplications) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(bpfnsapplicationsResource, c.ns, opts)) + +} + +// Create takes the representation of a bpfNsApplication and creates it. Returns the server's representation of the bpfNsApplication, and an error, if there is any. +func (c *FakeBpfNsApplications) Create(ctx context.Context, bpfNsApplication *v1alpha1.BpfNsApplication, opts v1.CreateOptions) (result *v1alpha1.BpfNsApplication, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(bpfnsapplicationsResource, c.ns, bpfNsApplication), &v1alpha1.BpfNsApplication{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsApplication), err +} + +// Update takes the representation of a bpfNsApplication and updates it. Returns the server's representation of the bpfNsApplication, and an error, if there is any. +func (c *FakeBpfNsApplications) Update(ctx context.Context, bpfNsApplication *v1alpha1.BpfNsApplication, opts v1.UpdateOptions) (result *v1alpha1.BpfNsApplication, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(bpfnsapplicationsResource, c.ns, bpfNsApplication), &v1alpha1.BpfNsApplication{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsApplication), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeBpfNsApplications) UpdateStatus(ctx context.Context, bpfNsApplication *v1alpha1.BpfNsApplication, opts v1.UpdateOptions) (*v1alpha1.BpfNsApplication, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(bpfnsapplicationsResource, "status", c.ns, bpfNsApplication), &v1alpha1.BpfNsApplication{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsApplication), err +} + +// Delete takes name of the bpfNsApplication and deletes it. Returns an error if one occurs. +func (c *FakeBpfNsApplications) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(bpfnsapplicationsResource, c.ns, name, opts), &v1alpha1.BpfNsApplication{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeBpfNsApplications) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(bpfnsapplicationsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.BpfNsApplicationList{}) + return err +} + +// Patch applies the patch and returns the patched bpfNsApplication. +func (c *FakeBpfNsApplications) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.BpfNsApplication, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(bpfnsapplicationsResource, c.ns, name, pt, data, subresources...), &v1alpha1.BpfNsApplication{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsApplication), err +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_bpfnsprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_bpfnsprogram.go new file mode 100644 index 000000000..2c029a7a0 --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_bpfnsprogram.go @@ -0,0 +1,141 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeBpfNsPrograms implements BpfNsProgramInterface +type FakeBpfNsPrograms struct { + Fake *FakeBpfmanV1alpha1 + ns string +} + +var bpfnsprogramsResource = v1alpha1.SchemeGroupVersion.WithResource("bpfnsprograms") + +var bpfnsprogramsKind = v1alpha1.SchemeGroupVersion.WithKind("BpfNsProgram") + +// Get takes name of the bpfNsProgram, and returns the corresponding bpfNsProgram object, and an error if there is any. +func (c *FakeBpfNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.BpfNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(bpfnsprogramsResource, c.ns, name), &v1alpha1.BpfNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsProgram), err +} + +// List takes label and field selectors, and returns the list of BpfNsPrograms that match those selectors. +func (c *FakeBpfNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.BpfNsProgramList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(bpfnsprogramsResource, bpfnsprogramsKind, c.ns, opts), &v1alpha1.BpfNsProgramList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.BpfNsProgramList{ListMeta: obj.(*v1alpha1.BpfNsProgramList).ListMeta} + for _, item := range obj.(*v1alpha1.BpfNsProgramList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested bpfNsPrograms. +func (c *FakeBpfNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(bpfnsprogramsResource, c.ns, opts)) + +} + +// Create takes the representation of a bpfNsProgram and creates it. Returns the server's representation of the bpfNsProgram, and an error, if there is any. +func (c *FakeBpfNsPrograms) Create(ctx context.Context, bpfNsProgram *v1alpha1.BpfNsProgram, opts v1.CreateOptions) (result *v1alpha1.BpfNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(bpfnsprogramsResource, c.ns, bpfNsProgram), &v1alpha1.BpfNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsProgram), err +} + +// Update takes the representation of a bpfNsProgram and updates it. Returns the server's representation of the bpfNsProgram, and an error, if there is any. +func (c *FakeBpfNsPrograms) Update(ctx context.Context, bpfNsProgram *v1alpha1.BpfNsProgram, opts v1.UpdateOptions) (result *v1alpha1.BpfNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(bpfnsprogramsResource, c.ns, bpfNsProgram), &v1alpha1.BpfNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsProgram), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeBpfNsPrograms) UpdateStatus(ctx context.Context, bpfNsProgram *v1alpha1.BpfNsProgram, opts v1.UpdateOptions) (*v1alpha1.BpfNsProgram, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(bpfnsprogramsResource, "status", c.ns, bpfNsProgram), &v1alpha1.BpfNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsProgram), err +} + +// Delete takes name of the bpfNsProgram and deletes it. Returns an error if one occurs. +func (c *FakeBpfNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(bpfnsprogramsResource, c.ns, name, opts), &v1alpha1.BpfNsProgram{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeBpfNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(bpfnsprogramsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.BpfNsProgramList{}) + return err +} + +// Patch applies the patch and returns the patched bpfNsProgram. +func (c *FakeBpfNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.BpfNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(bpfnsprogramsResource, c.ns, name, pt, data, subresources...), &v1alpha1.BpfNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsProgram), err +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_tcnsprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_tcnsprogram.go new file mode 100644 index 000000000..afc7b00ec --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_tcnsprogram.go @@ -0,0 +1,141 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeTcNsPrograms implements TcNsProgramInterface +type FakeTcNsPrograms struct { + Fake *FakeBpfmanV1alpha1 + ns string +} + +var tcnsprogramsResource = v1alpha1.SchemeGroupVersion.WithResource("tcnsprograms") + +var tcnsprogramsKind = v1alpha1.SchemeGroupVersion.WithKind("TcNsProgram") + +// Get takes name of the tcNsProgram, and returns the corresponding tcNsProgram object, and an error if there is any. +func (c *FakeTcNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.TcNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(tcnsprogramsResource, c.ns, name), &v1alpha1.TcNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcNsProgram), err +} + +// List takes label and field selectors, and returns the list of TcNsPrograms that match those selectors. +func (c *FakeTcNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.TcNsProgramList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(tcnsprogramsResource, tcnsprogramsKind, c.ns, opts), &v1alpha1.TcNsProgramList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.TcNsProgramList{ListMeta: obj.(*v1alpha1.TcNsProgramList).ListMeta} + for _, item := range obj.(*v1alpha1.TcNsProgramList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested tcNsPrograms. +func (c *FakeTcNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(tcnsprogramsResource, c.ns, opts)) + +} + +// Create takes the representation of a tcNsProgram and creates it. Returns the server's representation of the tcNsProgram, and an error, if there is any. +func (c *FakeTcNsPrograms) Create(ctx context.Context, tcNsProgram *v1alpha1.TcNsProgram, opts v1.CreateOptions) (result *v1alpha1.TcNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(tcnsprogramsResource, c.ns, tcNsProgram), &v1alpha1.TcNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcNsProgram), err +} + +// Update takes the representation of a tcNsProgram and updates it. Returns the server's representation of the tcNsProgram, and an error, if there is any. +func (c *FakeTcNsPrograms) Update(ctx context.Context, tcNsProgram *v1alpha1.TcNsProgram, opts v1.UpdateOptions) (result *v1alpha1.TcNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(tcnsprogramsResource, c.ns, tcNsProgram), &v1alpha1.TcNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcNsProgram), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeTcNsPrograms) UpdateStatus(ctx context.Context, tcNsProgram *v1alpha1.TcNsProgram, opts v1.UpdateOptions) (*v1alpha1.TcNsProgram, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(tcnsprogramsResource, "status", c.ns, tcNsProgram), &v1alpha1.TcNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcNsProgram), err +} + +// Delete takes name of the tcNsProgram and deletes it. Returns an error if one occurs. +func (c *FakeTcNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(tcnsprogramsResource, c.ns, name, opts), &v1alpha1.TcNsProgram{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeTcNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(tcnsprogramsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.TcNsProgramList{}) + return err +} + +// Patch applies the patch and returns the patched tcNsProgram. +func (c *FakeTcNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TcNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(tcnsprogramsResource, c.ns, name, pt, data, subresources...), &v1alpha1.TcNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcNsProgram), err +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_tcxnsprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_tcxnsprogram.go new file mode 100644 index 000000000..faeb6da68 --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_tcxnsprogram.go @@ -0,0 +1,141 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeTcxNsPrograms implements TcxNsProgramInterface +type FakeTcxNsPrograms struct { + Fake *FakeBpfmanV1alpha1 + ns string +} + +var tcxnsprogramsResource = v1alpha1.SchemeGroupVersion.WithResource("tcxnsprograms") + +var tcxnsprogramsKind = v1alpha1.SchemeGroupVersion.WithKind("TcxNsProgram") + +// Get takes name of the tcxNsProgram, and returns the corresponding tcxNsProgram object, and an error if there is any. +func (c *FakeTcxNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.TcxNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(tcxnsprogramsResource, c.ns, name), &v1alpha1.TcxNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcxNsProgram), err +} + +// List takes label and field selectors, and returns the list of TcxNsPrograms that match those selectors. +func (c *FakeTcxNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.TcxNsProgramList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(tcxnsprogramsResource, tcxnsprogramsKind, c.ns, opts), &v1alpha1.TcxNsProgramList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.TcxNsProgramList{ListMeta: obj.(*v1alpha1.TcxNsProgramList).ListMeta} + for _, item := range obj.(*v1alpha1.TcxNsProgramList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested tcxNsPrograms. +func (c *FakeTcxNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(tcxnsprogramsResource, c.ns, opts)) + +} + +// Create takes the representation of a tcxNsProgram and creates it. Returns the server's representation of the tcxNsProgram, and an error, if there is any. +func (c *FakeTcxNsPrograms) Create(ctx context.Context, tcxNsProgram *v1alpha1.TcxNsProgram, opts v1.CreateOptions) (result *v1alpha1.TcxNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(tcxnsprogramsResource, c.ns, tcxNsProgram), &v1alpha1.TcxNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcxNsProgram), err +} + +// Update takes the representation of a tcxNsProgram and updates it. Returns the server's representation of the tcxNsProgram, and an error, if there is any. +func (c *FakeTcxNsPrograms) Update(ctx context.Context, tcxNsProgram *v1alpha1.TcxNsProgram, opts v1.UpdateOptions) (result *v1alpha1.TcxNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(tcxnsprogramsResource, c.ns, tcxNsProgram), &v1alpha1.TcxNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcxNsProgram), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeTcxNsPrograms) UpdateStatus(ctx context.Context, tcxNsProgram *v1alpha1.TcxNsProgram, opts v1.UpdateOptions) (*v1alpha1.TcxNsProgram, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(tcxnsprogramsResource, "status", c.ns, tcxNsProgram), &v1alpha1.TcxNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcxNsProgram), err +} + +// Delete takes name of the tcxNsProgram and deletes it. Returns an error if one occurs. +func (c *FakeTcxNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(tcxnsprogramsResource, c.ns, name, opts), &v1alpha1.TcxNsProgram{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeTcxNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(tcxnsprogramsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.TcxNsProgramList{}) + return err +} + +// Patch applies the patch and returns the patched tcxNsProgram. +func (c *FakeTcxNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TcxNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(tcxnsprogramsResource, c.ns, name, pt, data, subresources...), &v1alpha1.TcxNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcxNsProgram), err +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_uprobensprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_uprobensprogram.go new file mode 100644 index 000000000..9166c453d --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_uprobensprogram.go @@ -0,0 +1,141 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeUprobeNsPrograms implements UprobeNsProgramInterface +type FakeUprobeNsPrograms struct { + Fake *FakeBpfmanV1alpha1 + ns string +} + +var uprobensprogramsResource = v1alpha1.SchemeGroupVersion.WithResource("uprobensprograms") + +var uprobensprogramsKind = v1alpha1.SchemeGroupVersion.WithKind("UprobeNsProgram") + +// Get takes name of the uprobeNsProgram, and returns the corresponding uprobeNsProgram object, and an error if there is any. +func (c *FakeUprobeNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.UprobeNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(uprobensprogramsResource, c.ns, name), &v1alpha1.UprobeNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.UprobeNsProgram), err +} + +// List takes label and field selectors, and returns the list of UprobeNsPrograms that match those selectors. +func (c *FakeUprobeNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.UprobeNsProgramList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(uprobensprogramsResource, uprobensprogramsKind, c.ns, opts), &v1alpha1.UprobeNsProgramList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.UprobeNsProgramList{ListMeta: obj.(*v1alpha1.UprobeNsProgramList).ListMeta} + for _, item := range obj.(*v1alpha1.UprobeNsProgramList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested uprobeNsPrograms. +func (c *FakeUprobeNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(uprobensprogramsResource, c.ns, opts)) + +} + +// Create takes the representation of a uprobeNsProgram and creates it. Returns the server's representation of the uprobeNsProgram, and an error, if there is any. +func (c *FakeUprobeNsPrograms) Create(ctx context.Context, uprobeNsProgram *v1alpha1.UprobeNsProgram, opts v1.CreateOptions) (result *v1alpha1.UprobeNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(uprobensprogramsResource, c.ns, uprobeNsProgram), &v1alpha1.UprobeNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.UprobeNsProgram), err +} + +// Update takes the representation of a uprobeNsProgram and updates it. Returns the server's representation of the uprobeNsProgram, and an error, if there is any. +func (c *FakeUprobeNsPrograms) Update(ctx context.Context, uprobeNsProgram *v1alpha1.UprobeNsProgram, opts v1.UpdateOptions) (result *v1alpha1.UprobeNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(uprobensprogramsResource, c.ns, uprobeNsProgram), &v1alpha1.UprobeNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.UprobeNsProgram), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeUprobeNsPrograms) UpdateStatus(ctx context.Context, uprobeNsProgram *v1alpha1.UprobeNsProgram, opts v1.UpdateOptions) (*v1alpha1.UprobeNsProgram, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(uprobensprogramsResource, "status", c.ns, uprobeNsProgram), &v1alpha1.UprobeNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.UprobeNsProgram), err +} + +// Delete takes name of the uprobeNsProgram and deletes it. Returns an error if one occurs. +func (c *FakeUprobeNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(uprobensprogramsResource, c.ns, name, opts), &v1alpha1.UprobeNsProgram{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeUprobeNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(uprobensprogramsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.UprobeNsProgramList{}) + return err +} + +// Patch applies the patch and returns the patched uprobeNsProgram. +func (c *FakeUprobeNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.UprobeNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(uprobensprogramsResource, c.ns, name, pt, data, subresources...), &v1alpha1.UprobeNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.UprobeNsProgram), err +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_xdpnsprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_xdpnsprogram.go new file mode 100644 index 000000000..ef9ee7dc8 --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_xdpnsprogram.go @@ -0,0 +1,141 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeXdpNsPrograms implements XdpNsProgramInterface +type FakeXdpNsPrograms struct { + Fake *FakeBpfmanV1alpha1 + ns string +} + +var xdpnsprogramsResource = v1alpha1.SchemeGroupVersion.WithResource("xdpnsprograms") + +var xdpnsprogramsKind = v1alpha1.SchemeGroupVersion.WithKind("XdpNsProgram") + +// Get takes name of the xdpNsProgram, and returns the corresponding xdpNsProgram object, and an error if there is any. +func (c *FakeXdpNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.XdpNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(xdpnsprogramsResource, c.ns, name), &v1alpha1.XdpNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.XdpNsProgram), err +} + +// List takes label and field selectors, and returns the list of XdpNsPrograms that match those selectors. +func (c *FakeXdpNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.XdpNsProgramList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(xdpnsprogramsResource, xdpnsprogramsKind, c.ns, opts), &v1alpha1.XdpNsProgramList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.XdpNsProgramList{ListMeta: obj.(*v1alpha1.XdpNsProgramList).ListMeta} + for _, item := range obj.(*v1alpha1.XdpNsProgramList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested xdpNsPrograms. +func (c *FakeXdpNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(xdpnsprogramsResource, c.ns, opts)) + +} + +// Create takes the representation of a xdpNsProgram and creates it. Returns the server's representation of the xdpNsProgram, and an error, if there is any. +func (c *FakeXdpNsPrograms) Create(ctx context.Context, xdpNsProgram *v1alpha1.XdpNsProgram, opts v1.CreateOptions) (result *v1alpha1.XdpNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(xdpnsprogramsResource, c.ns, xdpNsProgram), &v1alpha1.XdpNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.XdpNsProgram), err +} + +// Update takes the representation of a xdpNsProgram and updates it. Returns the server's representation of the xdpNsProgram, and an error, if there is any. +func (c *FakeXdpNsPrograms) Update(ctx context.Context, xdpNsProgram *v1alpha1.XdpNsProgram, opts v1.UpdateOptions) (result *v1alpha1.XdpNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(xdpnsprogramsResource, c.ns, xdpNsProgram), &v1alpha1.XdpNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.XdpNsProgram), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeXdpNsPrograms) UpdateStatus(ctx context.Context, xdpNsProgram *v1alpha1.XdpNsProgram, opts v1.UpdateOptions) (*v1alpha1.XdpNsProgram, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(xdpnsprogramsResource, "status", c.ns, xdpNsProgram), &v1alpha1.XdpNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.XdpNsProgram), err +} + +// Delete takes name of the xdpNsProgram and deletes it. Returns an error if one occurs. +func (c *FakeXdpNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(xdpnsprogramsResource, c.ns, name, opts), &v1alpha1.XdpNsProgram{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeXdpNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(xdpnsprogramsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.XdpNsProgramList{}) + return err +} + +// Patch applies the patch and returns the patched xdpNsProgram. +func (c *FakeXdpNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.XdpNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(xdpnsprogramsResource, c.ns, name, pt, data, subresources...), &v1alpha1.XdpNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.XdpNsProgram), err +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/generated_expansion.go b/pkg/client/clientset/typed/apis/v1alpha1/generated_expansion.go index c514790d0..3b956a1a6 100644 --- a/pkg/client/clientset/typed/apis/v1alpha1/generated_expansion.go +++ b/pkg/client/clientset/typed/apis/v1alpha1/generated_expansion.go @@ -20,6 +20,10 @@ package v1alpha1 type BpfApplicationExpansion interface{} +type BpfNsApplicationExpansion interface{} + +type BpfNsProgramExpansion interface{} + type BpfProgramExpansion interface{} type FentryProgramExpansion interface{} @@ -28,12 +32,20 @@ type FexitProgramExpansion interface{} type KprobeProgramExpansion interface{} +type TcNsProgramExpansion interface{} + type TcProgramExpansion interface{} +type TcxNsProgramExpansion interface{} + type TcxProgramExpansion interface{} type TracepointProgramExpansion interface{} +type UprobeNsProgramExpansion interface{} + type UprobeProgramExpansion interface{} +type XdpNsProgramExpansion interface{} + type XdpProgramExpansion interface{} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/tcnsprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/tcnsprogram.go new file mode 100644 index 000000000..9795fa01a --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/tcnsprogram.go @@ -0,0 +1,195 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + scheme "github.com/bpfman/bpfman-operator/pkg/client/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// TcNsProgramsGetter has a method to return a TcNsProgramInterface. +// A group's client should implement this interface. +type TcNsProgramsGetter interface { + TcNsPrograms(namespace string) TcNsProgramInterface +} + +// TcNsProgramInterface has methods to work with TcNsProgram resources. +type TcNsProgramInterface interface { + Create(ctx context.Context, tcNsProgram *v1alpha1.TcNsProgram, opts v1.CreateOptions) (*v1alpha1.TcNsProgram, error) + Update(ctx context.Context, tcNsProgram *v1alpha1.TcNsProgram, opts v1.UpdateOptions) (*v1alpha1.TcNsProgram, error) + UpdateStatus(ctx context.Context, tcNsProgram *v1alpha1.TcNsProgram, opts v1.UpdateOptions) (*v1alpha1.TcNsProgram, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.TcNsProgram, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.TcNsProgramList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TcNsProgram, err error) + TcNsProgramExpansion +} + +// tcNsPrograms implements TcNsProgramInterface +type tcNsPrograms struct { + client rest.Interface + ns string +} + +// newTcNsPrograms returns a TcNsPrograms +func newTcNsPrograms(c *BpfmanV1alpha1Client, namespace string) *tcNsPrograms { + return &tcNsPrograms{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the tcNsProgram, and returns the corresponding tcNsProgram object, and an error if there is any. +func (c *tcNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.TcNsProgram, err error) { + result = &v1alpha1.TcNsProgram{} + err = c.client.Get(). + Namespace(c.ns). + Resource("tcnsprograms"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of TcNsPrograms that match those selectors. +func (c *tcNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.TcNsProgramList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.TcNsProgramList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("tcnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested tcNsPrograms. +func (c *tcNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("tcnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a tcNsProgram and creates it. Returns the server's representation of the tcNsProgram, and an error, if there is any. +func (c *tcNsPrograms) Create(ctx context.Context, tcNsProgram *v1alpha1.TcNsProgram, opts v1.CreateOptions) (result *v1alpha1.TcNsProgram, err error) { + result = &v1alpha1.TcNsProgram{} + err = c.client.Post(). + Namespace(c.ns). + Resource("tcnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(tcNsProgram). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a tcNsProgram and updates it. Returns the server's representation of the tcNsProgram, and an error, if there is any. +func (c *tcNsPrograms) Update(ctx context.Context, tcNsProgram *v1alpha1.TcNsProgram, opts v1.UpdateOptions) (result *v1alpha1.TcNsProgram, err error) { + result = &v1alpha1.TcNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("tcnsprograms"). + Name(tcNsProgram.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(tcNsProgram). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *tcNsPrograms) UpdateStatus(ctx context.Context, tcNsProgram *v1alpha1.TcNsProgram, opts v1.UpdateOptions) (result *v1alpha1.TcNsProgram, err error) { + result = &v1alpha1.TcNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("tcnsprograms"). + Name(tcNsProgram.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(tcNsProgram). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the tcNsProgram and deletes it. Returns an error if one occurs. +func (c *tcNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("tcnsprograms"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *tcNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("tcnsprograms"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched tcNsProgram. +func (c *tcNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TcNsProgram, err error) { + result = &v1alpha1.TcNsProgram{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("tcnsprograms"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/tcxnsprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/tcxnsprogram.go new file mode 100644 index 000000000..2c2ec75c6 --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/tcxnsprogram.go @@ -0,0 +1,195 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + scheme "github.com/bpfman/bpfman-operator/pkg/client/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// TcxNsProgramsGetter has a method to return a TcxNsProgramInterface. +// A group's client should implement this interface. +type TcxNsProgramsGetter interface { + TcxNsPrograms(namespace string) TcxNsProgramInterface +} + +// TcxNsProgramInterface has methods to work with TcxNsProgram resources. +type TcxNsProgramInterface interface { + Create(ctx context.Context, tcxNsProgram *v1alpha1.TcxNsProgram, opts v1.CreateOptions) (*v1alpha1.TcxNsProgram, error) + Update(ctx context.Context, tcxNsProgram *v1alpha1.TcxNsProgram, opts v1.UpdateOptions) (*v1alpha1.TcxNsProgram, error) + UpdateStatus(ctx context.Context, tcxNsProgram *v1alpha1.TcxNsProgram, opts v1.UpdateOptions) (*v1alpha1.TcxNsProgram, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.TcxNsProgram, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.TcxNsProgramList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TcxNsProgram, err error) + TcxNsProgramExpansion +} + +// tcxNsPrograms implements TcxNsProgramInterface +type tcxNsPrograms struct { + client rest.Interface + ns string +} + +// newTcxNsPrograms returns a TcxNsPrograms +func newTcxNsPrograms(c *BpfmanV1alpha1Client, namespace string) *tcxNsPrograms { + return &tcxNsPrograms{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the tcxNsProgram, and returns the corresponding tcxNsProgram object, and an error if there is any. +func (c *tcxNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.TcxNsProgram, err error) { + result = &v1alpha1.TcxNsProgram{} + err = c.client.Get(). + Namespace(c.ns). + Resource("tcxnsprograms"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of TcxNsPrograms that match those selectors. +func (c *tcxNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.TcxNsProgramList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.TcxNsProgramList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("tcxnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested tcxNsPrograms. +func (c *tcxNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("tcxnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a tcxNsProgram and creates it. Returns the server's representation of the tcxNsProgram, and an error, if there is any. +func (c *tcxNsPrograms) Create(ctx context.Context, tcxNsProgram *v1alpha1.TcxNsProgram, opts v1.CreateOptions) (result *v1alpha1.TcxNsProgram, err error) { + result = &v1alpha1.TcxNsProgram{} + err = c.client.Post(). + Namespace(c.ns). + Resource("tcxnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(tcxNsProgram). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a tcxNsProgram and updates it. Returns the server's representation of the tcxNsProgram, and an error, if there is any. +func (c *tcxNsPrograms) Update(ctx context.Context, tcxNsProgram *v1alpha1.TcxNsProgram, opts v1.UpdateOptions) (result *v1alpha1.TcxNsProgram, err error) { + result = &v1alpha1.TcxNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("tcxnsprograms"). + Name(tcxNsProgram.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(tcxNsProgram). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *tcxNsPrograms) UpdateStatus(ctx context.Context, tcxNsProgram *v1alpha1.TcxNsProgram, opts v1.UpdateOptions) (result *v1alpha1.TcxNsProgram, err error) { + result = &v1alpha1.TcxNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("tcxnsprograms"). + Name(tcxNsProgram.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(tcxNsProgram). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the tcxNsProgram and deletes it. Returns an error if one occurs. +func (c *tcxNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("tcxnsprograms"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *tcxNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("tcxnsprograms"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched tcxNsProgram. +func (c *tcxNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TcxNsProgram, err error) { + result = &v1alpha1.TcxNsProgram{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("tcxnsprograms"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/uprobensprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/uprobensprogram.go new file mode 100644 index 000000000..824f80991 --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/uprobensprogram.go @@ -0,0 +1,195 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + scheme "github.com/bpfman/bpfman-operator/pkg/client/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// UprobeNsProgramsGetter has a method to return a UprobeNsProgramInterface. +// A group's client should implement this interface. +type UprobeNsProgramsGetter interface { + UprobeNsPrograms(namespace string) UprobeNsProgramInterface +} + +// UprobeNsProgramInterface has methods to work with UprobeNsProgram resources. +type UprobeNsProgramInterface interface { + Create(ctx context.Context, uprobeNsProgram *v1alpha1.UprobeNsProgram, opts v1.CreateOptions) (*v1alpha1.UprobeNsProgram, error) + Update(ctx context.Context, uprobeNsProgram *v1alpha1.UprobeNsProgram, opts v1.UpdateOptions) (*v1alpha1.UprobeNsProgram, error) + UpdateStatus(ctx context.Context, uprobeNsProgram *v1alpha1.UprobeNsProgram, opts v1.UpdateOptions) (*v1alpha1.UprobeNsProgram, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.UprobeNsProgram, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.UprobeNsProgramList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.UprobeNsProgram, err error) + UprobeNsProgramExpansion +} + +// uprobeNsPrograms implements UprobeNsProgramInterface +type uprobeNsPrograms struct { + client rest.Interface + ns string +} + +// newUprobeNsPrograms returns a UprobeNsPrograms +func newUprobeNsPrograms(c *BpfmanV1alpha1Client, namespace string) *uprobeNsPrograms { + return &uprobeNsPrograms{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the uprobeNsProgram, and returns the corresponding uprobeNsProgram object, and an error if there is any. +func (c *uprobeNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.UprobeNsProgram, err error) { + result = &v1alpha1.UprobeNsProgram{} + err = c.client.Get(). + Namespace(c.ns). + Resource("uprobensprograms"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of UprobeNsPrograms that match those selectors. +func (c *uprobeNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.UprobeNsProgramList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.UprobeNsProgramList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("uprobensprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested uprobeNsPrograms. +func (c *uprobeNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("uprobensprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a uprobeNsProgram and creates it. Returns the server's representation of the uprobeNsProgram, and an error, if there is any. +func (c *uprobeNsPrograms) Create(ctx context.Context, uprobeNsProgram *v1alpha1.UprobeNsProgram, opts v1.CreateOptions) (result *v1alpha1.UprobeNsProgram, err error) { + result = &v1alpha1.UprobeNsProgram{} + err = c.client.Post(). + Namespace(c.ns). + Resource("uprobensprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(uprobeNsProgram). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a uprobeNsProgram and updates it. Returns the server's representation of the uprobeNsProgram, and an error, if there is any. +func (c *uprobeNsPrograms) Update(ctx context.Context, uprobeNsProgram *v1alpha1.UprobeNsProgram, opts v1.UpdateOptions) (result *v1alpha1.UprobeNsProgram, err error) { + result = &v1alpha1.UprobeNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("uprobensprograms"). + Name(uprobeNsProgram.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(uprobeNsProgram). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *uprobeNsPrograms) UpdateStatus(ctx context.Context, uprobeNsProgram *v1alpha1.UprobeNsProgram, opts v1.UpdateOptions) (result *v1alpha1.UprobeNsProgram, err error) { + result = &v1alpha1.UprobeNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("uprobensprograms"). + Name(uprobeNsProgram.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(uprobeNsProgram). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the uprobeNsProgram and deletes it. Returns an error if one occurs. +func (c *uprobeNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("uprobensprograms"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *uprobeNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("uprobensprograms"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched uprobeNsProgram. +func (c *uprobeNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.UprobeNsProgram, err error) { + result = &v1alpha1.UprobeNsProgram{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("uprobensprograms"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/xdpnsprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/xdpnsprogram.go new file mode 100644 index 000000000..09742d71d --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/xdpnsprogram.go @@ -0,0 +1,195 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + scheme "github.com/bpfman/bpfman-operator/pkg/client/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// XdpNsProgramsGetter has a method to return a XdpNsProgramInterface. +// A group's client should implement this interface. +type XdpNsProgramsGetter interface { + XdpNsPrograms(namespace string) XdpNsProgramInterface +} + +// XdpNsProgramInterface has methods to work with XdpNsProgram resources. +type XdpNsProgramInterface interface { + Create(ctx context.Context, xdpNsProgram *v1alpha1.XdpNsProgram, opts v1.CreateOptions) (*v1alpha1.XdpNsProgram, error) + Update(ctx context.Context, xdpNsProgram *v1alpha1.XdpNsProgram, opts v1.UpdateOptions) (*v1alpha1.XdpNsProgram, error) + UpdateStatus(ctx context.Context, xdpNsProgram *v1alpha1.XdpNsProgram, opts v1.UpdateOptions) (*v1alpha1.XdpNsProgram, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.XdpNsProgram, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.XdpNsProgramList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.XdpNsProgram, err error) + XdpNsProgramExpansion +} + +// xdpNsPrograms implements XdpNsProgramInterface +type xdpNsPrograms struct { + client rest.Interface + ns string +} + +// newXdpNsPrograms returns a XdpNsPrograms +func newXdpNsPrograms(c *BpfmanV1alpha1Client, namespace string) *xdpNsPrograms { + return &xdpNsPrograms{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the xdpNsProgram, and returns the corresponding xdpNsProgram object, and an error if there is any. +func (c *xdpNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.XdpNsProgram, err error) { + result = &v1alpha1.XdpNsProgram{} + err = c.client.Get(). + Namespace(c.ns). + Resource("xdpnsprograms"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of XdpNsPrograms that match those selectors. +func (c *xdpNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.XdpNsProgramList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.XdpNsProgramList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("xdpnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested xdpNsPrograms. +func (c *xdpNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("xdpnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a xdpNsProgram and creates it. Returns the server's representation of the xdpNsProgram, and an error, if there is any. +func (c *xdpNsPrograms) Create(ctx context.Context, xdpNsProgram *v1alpha1.XdpNsProgram, opts v1.CreateOptions) (result *v1alpha1.XdpNsProgram, err error) { + result = &v1alpha1.XdpNsProgram{} + err = c.client.Post(). + Namespace(c.ns). + Resource("xdpnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(xdpNsProgram). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a xdpNsProgram and updates it. Returns the server's representation of the xdpNsProgram, and an error, if there is any. +func (c *xdpNsPrograms) Update(ctx context.Context, xdpNsProgram *v1alpha1.XdpNsProgram, opts v1.UpdateOptions) (result *v1alpha1.XdpNsProgram, err error) { + result = &v1alpha1.XdpNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("xdpnsprograms"). + Name(xdpNsProgram.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(xdpNsProgram). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *xdpNsPrograms) UpdateStatus(ctx context.Context, xdpNsProgram *v1alpha1.XdpNsProgram, opts v1.UpdateOptions) (result *v1alpha1.XdpNsProgram, err error) { + result = &v1alpha1.XdpNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("xdpnsprograms"). + Name(xdpNsProgram.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(xdpNsProgram). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the xdpNsProgram and deletes it. Returns an error if one occurs. +func (c *xdpNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("xdpnsprograms"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *xdpNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("xdpnsprograms"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched xdpNsProgram. +func (c *xdpNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.XdpNsProgram, err error) { + result = &v1alpha1.XdpNsProgram{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("xdpnsprograms"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/externalversions/apis/v1alpha1/bpfnsapplication.go b/pkg/client/externalversions/apis/v1alpha1/bpfnsapplication.go new file mode 100644 index 000000000..d8b075589 --- /dev/null +++ b/pkg/client/externalversions/apis/v1alpha1/bpfnsapplication.go @@ -0,0 +1,90 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + apisv1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1alpha1 "github.com/bpfman/bpfman-operator/pkg/client/apis/v1alpha1" + clientset "github.com/bpfman/bpfman-operator/pkg/client/clientset" + internalinterfaces "github.com/bpfman/bpfman-operator/pkg/client/externalversions/internalinterfaces" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// BpfNsApplicationInformer provides access to a shared informer and lister for +// BpfNsApplications. +type BpfNsApplicationInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.BpfNsApplicationLister +} + +type bpfNsApplicationInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewBpfNsApplicationInformer constructs a new informer for BpfNsApplication type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewBpfNsApplicationInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredBpfNsApplicationInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredBpfNsApplicationInformer constructs a new informer for BpfNsApplication type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredBpfNsApplicationInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().BpfNsApplications(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().BpfNsApplications(namespace).Watch(context.TODO(), options) + }, + }, + &apisv1alpha1.BpfNsApplication{}, + resyncPeriod, + indexers, + ) +} + +func (f *bpfNsApplicationInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredBpfNsApplicationInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *bpfNsApplicationInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisv1alpha1.BpfNsApplication{}, f.defaultInformer) +} + +func (f *bpfNsApplicationInformer) Lister() v1alpha1.BpfNsApplicationLister { + return v1alpha1.NewBpfNsApplicationLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/externalversions/apis/v1alpha1/bpfnsprogram.go b/pkg/client/externalversions/apis/v1alpha1/bpfnsprogram.go new file mode 100644 index 000000000..d03eeb308 --- /dev/null +++ b/pkg/client/externalversions/apis/v1alpha1/bpfnsprogram.go @@ -0,0 +1,90 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + apisv1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1alpha1 "github.com/bpfman/bpfman-operator/pkg/client/apis/v1alpha1" + clientset "github.com/bpfman/bpfman-operator/pkg/client/clientset" + internalinterfaces "github.com/bpfman/bpfman-operator/pkg/client/externalversions/internalinterfaces" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// BpfNsProgramInformer provides access to a shared informer and lister for +// BpfNsPrograms. +type BpfNsProgramInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.BpfNsProgramLister +} + +type bpfNsProgramInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewBpfNsProgramInformer constructs a new informer for BpfNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewBpfNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredBpfNsProgramInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredBpfNsProgramInformer constructs a new informer for BpfNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredBpfNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().BpfNsPrograms(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().BpfNsPrograms(namespace).Watch(context.TODO(), options) + }, + }, + &apisv1alpha1.BpfNsProgram{}, + resyncPeriod, + indexers, + ) +} + +func (f *bpfNsProgramInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredBpfNsProgramInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *bpfNsProgramInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisv1alpha1.BpfNsProgram{}, f.defaultInformer) +} + +func (f *bpfNsProgramInformer) Lister() v1alpha1.BpfNsProgramLister { + return v1alpha1.NewBpfNsProgramLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/externalversions/apis/v1alpha1/interface.go b/pkg/client/externalversions/apis/v1alpha1/interface.go index 432597f9d..7ca2dcbec 100644 --- a/pkg/client/externalversions/apis/v1alpha1/interface.go +++ b/pkg/client/externalversions/apis/v1alpha1/interface.go @@ -26,6 +26,10 @@ import ( type Interface interface { // BpfApplications returns a BpfApplicationInformer. BpfApplications() BpfApplicationInformer + // BpfNsApplications returns a BpfNsApplicationInformer. + BpfNsApplications() BpfNsApplicationInformer + // BpfNsPrograms returns a BpfNsProgramInformer. + BpfNsPrograms() BpfNsProgramInformer // BpfPrograms returns a BpfProgramInformer. BpfPrograms() BpfProgramInformer // FentryPrograms returns a FentryProgramInformer. @@ -34,14 +38,22 @@ type Interface interface { FexitPrograms() FexitProgramInformer // KprobePrograms returns a KprobeProgramInformer. KprobePrograms() KprobeProgramInformer + // TcNsPrograms returns a TcNsProgramInformer. + TcNsPrograms() TcNsProgramInformer // TcPrograms returns a TcProgramInformer. TcPrograms() TcProgramInformer + // TcxNsPrograms returns a TcxNsProgramInformer. + TcxNsPrograms() TcxNsProgramInformer // TcxPrograms returns a TcxProgramInformer. TcxPrograms() TcxProgramInformer // TracepointPrograms returns a TracepointProgramInformer. TracepointPrograms() TracepointProgramInformer + // UprobeNsPrograms returns a UprobeNsProgramInformer. + UprobeNsPrograms() UprobeNsProgramInformer // UprobePrograms returns a UprobeProgramInformer. UprobePrograms() UprobeProgramInformer + // XdpNsPrograms returns a XdpNsProgramInformer. + XdpNsPrograms() XdpNsProgramInformer // XdpPrograms returns a XdpProgramInformer. XdpPrograms() XdpProgramInformer } @@ -62,6 +74,16 @@ func (v *version) BpfApplications() BpfApplicationInformer { return &bpfApplicationInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// BpfNsApplications returns a BpfNsApplicationInformer. +func (v *version) BpfNsApplications() BpfNsApplicationInformer { + return &bpfNsApplicationInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// BpfNsPrograms returns a BpfNsProgramInformer. +func (v *version) BpfNsPrograms() BpfNsProgramInformer { + return &bpfNsProgramInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // BpfPrograms returns a BpfProgramInformer. func (v *version) BpfPrograms() BpfProgramInformer { return &bpfProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} @@ -82,11 +104,21 @@ func (v *version) KprobePrograms() KprobeProgramInformer { return &kprobeProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// TcNsPrograms returns a TcNsProgramInformer. +func (v *version) TcNsPrograms() TcNsProgramInformer { + return &tcNsProgramInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // TcPrograms returns a TcProgramInformer. func (v *version) TcPrograms() TcProgramInformer { return &tcProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// TcxNsPrograms returns a TcxNsProgramInformer. +func (v *version) TcxNsPrograms() TcxNsProgramInformer { + return &tcxNsProgramInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // TcxPrograms returns a TcxProgramInformer. func (v *version) TcxPrograms() TcxProgramInformer { return &tcxProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} @@ -97,11 +129,21 @@ func (v *version) TracepointPrograms() TracepointProgramInformer { return &tracepointProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// UprobeNsPrograms returns a UprobeNsProgramInformer. +func (v *version) UprobeNsPrograms() UprobeNsProgramInformer { + return &uprobeNsProgramInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // UprobePrograms returns a UprobeProgramInformer. func (v *version) UprobePrograms() UprobeProgramInformer { return &uprobeProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// XdpNsPrograms returns a XdpNsProgramInformer. +func (v *version) XdpNsPrograms() XdpNsProgramInformer { + return &xdpNsProgramInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // XdpPrograms returns a XdpProgramInformer. func (v *version) XdpPrograms() XdpProgramInformer { return &xdpProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} diff --git a/pkg/client/externalversions/apis/v1alpha1/tcnsprogram.go b/pkg/client/externalversions/apis/v1alpha1/tcnsprogram.go new file mode 100644 index 000000000..3f99fd4e3 --- /dev/null +++ b/pkg/client/externalversions/apis/v1alpha1/tcnsprogram.go @@ -0,0 +1,90 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + apisv1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1alpha1 "github.com/bpfman/bpfman-operator/pkg/client/apis/v1alpha1" + clientset "github.com/bpfman/bpfman-operator/pkg/client/clientset" + internalinterfaces "github.com/bpfman/bpfman-operator/pkg/client/externalversions/internalinterfaces" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// TcNsProgramInformer provides access to a shared informer and lister for +// TcNsPrograms. +type TcNsProgramInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.TcNsProgramLister +} + +type tcNsProgramInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewTcNsProgramInformer constructs a new informer for TcNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewTcNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredTcNsProgramInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredTcNsProgramInformer constructs a new informer for TcNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredTcNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().TcNsPrograms(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().TcNsPrograms(namespace).Watch(context.TODO(), options) + }, + }, + &apisv1alpha1.TcNsProgram{}, + resyncPeriod, + indexers, + ) +} + +func (f *tcNsProgramInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredTcNsProgramInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *tcNsProgramInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisv1alpha1.TcNsProgram{}, f.defaultInformer) +} + +func (f *tcNsProgramInformer) Lister() v1alpha1.TcNsProgramLister { + return v1alpha1.NewTcNsProgramLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/externalversions/apis/v1alpha1/tcxnsprogram.go b/pkg/client/externalversions/apis/v1alpha1/tcxnsprogram.go new file mode 100644 index 000000000..d77878c0d --- /dev/null +++ b/pkg/client/externalversions/apis/v1alpha1/tcxnsprogram.go @@ -0,0 +1,90 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + apisv1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1alpha1 "github.com/bpfman/bpfman-operator/pkg/client/apis/v1alpha1" + clientset "github.com/bpfman/bpfman-operator/pkg/client/clientset" + internalinterfaces "github.com/bpfman/bpfman-operator/pkg/client/externalversions/internalinterfaces" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// TcxNsProgramInformer provides access to a shared informer and lister for +// TcxNsPrograms. +type TcxNsProgramInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.TcxNsProgramLister +} + +type tcxNsProgramInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewTcxNsProgramInformer constructs a new informer for TcxNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewTcxNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredTcxNsProgramInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredTcxNsProgramInformer constructs a new informer for TcxNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredTcxNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().TcxNsPrograms(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().TcxNsPrograms(namespace).Watch(context.TODO(), options) + }, + }, + &apisv1alpha1.TcxNsProgram{}, + resyncPeriod, + indexers, + ) +} + +func (f *tcxNsProgramInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredTcxNsProgramInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *tcxNsProgramInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisv1alpha1.TcxNsProgram{}, f.defaultInformer) +} + +func (f *tcxNsProgramInformer) Lister() v1alpha1.TcxNsProgramLister { + return v1alpha1.NewTcxNsProgramLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/externalversions/apis/v1alpha1/uprobensprogram.go b/pkg/client/externalversions/apis/v1alpha1/uprobensprogram.go new file mode 100644 index 000000000..d842b9ece --- /dev/null +++ b/pkg/client/externalversions/apis/v1alpha1/uprobensprogram.go @@ -0,0 +1,90 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + apisv1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1alpha1 "github.com/bpfman/bpfman-operator/pkg/client/apis/v1alpha1" + clientset "github.com/bpfman/bpfman-operator/pkg/client/clientset" + internalinterfaces "github.com/bpfman/bpfman-operator/pkg/client/externalversions/internalinterfaces" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// UprobeNsProgramInformer provides access to a shared informer and lister for +// UprobeNsPrograms. +type UprobeNsProgramInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.UprobeNsProgramLister +} + +type uprobeNsProgramInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewUprobeNsProgramInformer constructs a new informer for UprobeNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewUprobeNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredUprobeNsProgramInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredUprobeNsProgramInformer constructs a new informer for UprobeNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredUprobeNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().UprobeNsPrograms(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().UprobeNsPrograms(namespace).Watch(context.TODO(), options) + }, + }, + &apisv1alpha1.UprobeNsProgram{}, + resyncPeriod, + indexers, + ) +} + +func (f *uprobeNsProgramInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredUprobeNsProgramInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *uprobeNsProgramInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisv1alpha1.UprobeNsProgram{}, f.defaultInformer) +} + +func (f *uprobeNsProgramInformer) Lister() v1alpha1.UprobeNsProgramLister { + return v1alpha1.NewUprobeNsProgramLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/externalversions/apis/v1alpha1/xdpnsprogram.go b/pkg/client/externalversions/apis/v1alpha1/xdpnsprogram.go new file mode 100644 index 000000000..b43c10380 --- /dev/null +++ b/pkg/client/externalversions/apis/v1alpha1/xdpnsprogram.go @@ -0,0 +1,90 @@ +/* +Copyright 2023 The bpfman Authors. + +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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + apisv1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1alpha1 "github.com/bpfman/bpfman-operator/pkg/client/apis/v1alpha1" + clientset "github.com/bpfman/bpfman-operator/pkg/client/clientset" + internalinterfaces "github.com/bpfman/bpfman-operator/pkg/client/externalversions/internalinterfaces" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// XdpNsProgramInformer provides access to a shared informer and lister for +// XdpNsPrograms. +type XdpNsProgramInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.XdpNsProgramLister +} + +type xdpNsProgramInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewXdpNsProgramInformer constructs a new informer for XdpNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewXdpNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredXdpNsProgramInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredXdpNsProgramInformer constructs a new informer for XdpNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredXdpNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().XdpNsPrograms(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().XdpNsPrograms(namespace).Watch(context.TODO(), options) + }, + }, + &apisv1alpha1.XdpNsProgram{}, + resyncPeriod, + indexers, + ) +} + +func (f *xdpNsProgramInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredXdpNsProgramInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *xdpNsProgramInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisv1alpha1.XdpNsProgram{}, f.defaultInformer) +} + +func (f *xdpNsProgramInformer) Lister() v1alpha1.XdpNsProgramLister { + return v1alpha1.NewXdpNsProgramLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/externalversions/generic.go b/pkg/client/externalversions/generic.go index 4db1e4a1e..2d084ef57 100644 --- a/pkg/client/externalversions/generic.go +++ b/pkg/client/externalversions/generic.go @@ -55,6 +55,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource // Group=bpfman.io, Version=v1alpha1 case v1alpha1.SchemeGroupVersion.WithResource("bpfapplications"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().BpfApplications().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("bpfnsapplications"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().BpfNsApplications().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("bpfnsprograms"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().BpfNsPrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("bpfprograms"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().BpfPrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("fentryprograms"): @@ -63,14 +67,22 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().FexitPrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("kprobeprograms"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().KprobePrograms().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("tcnsprograms"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().TcNsPrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("tcprograms"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().TcPrograms().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("tcxnsprograms"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().TcxNsPrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("tcxprograms"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().TcxPrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("tracepointprograms"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().TracepointPrograms().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("uprobensprograms"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().UprobeNsPrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("uprobeprograms"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().UprobePrograms().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("xdpnsprograms"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().XdpNsPrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("xdpprograms"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().XdpPrograms().Informer()}, nil