diff --git a/PROJECT b/PROJECT index 89787a1ad..f4d69833a 100644 --- a/PROJECT +++ b/PROJECT @@ -1,74 +1,78 @@ +# Code generated by tool. DO NOT EDIT. +# This file is used to track the info used to scaffold your project +# and allow the plugins properly work. +# 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 - namespaced: false - controller: true - domain: bpfman.io - kind: XdpProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1 - namespaced: false - controller: true - domain: bpfman.io - kind: TcProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1 - namespaced: false - controller: true - domain: bpfman.io - kind: TracePointProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1 - namespaced: false - controller: true - domain: bpfman.io - kind: KprobeProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1 - namespaced: false - controller: true - domain: bpfman.io - kind: UprobeProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1 - namespaced: false - controller: true - domain: bpfman.io - kind: FentryProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1 - namespaced: false - controller: true - domain: bpfman.io - kind: FexitProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 + - 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: 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 version: "3" diff --git a/README.md b/README.md index 3dde146a2..01632884c 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,12 @@ bpfman supports: * `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, diff --git a/apis/v1alpha1/bpfapplication_types.go b/apis/v1alpha1/bpfapplication_types.go new file mode 100644 index 000000000..6b28064cd --- /dev/null +++ b/apis/v1alpha1/bpfapplication_types.go @@ -0,0 +1,168 @@ +/* +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. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EBPFProgType defines the supported eBPF program types +type EBPFProgType string + +const ( + // ProgTypeXDP refers to the XDP program type. + ProgTypeXDP EBPFProgType = "XDP" + + // ProgTypeTC refers to the TC program type. + ProgTypeTC EBPFProgType = "TC" + + // ProgTypeTCX refers to the TCx program type. + ProgTypeTCX EBPFProgType = "TCX" + + // ProgTypeFentry refers to the Fentry program type. + ProgTypeFentry EBPFProgType = "Fentry" + + // ProgTypeFexit refers to the Fexit program type. + ProgTypeFexit EBPFProgType = "Fexit" + + // ProgTypeKprobe refers to the Kprobe program type. + ProgTypeKprobe EBPFProgType = "Kprobe" + + // ProgTypeKretprobe refers to the Kprobe program type. + ProgTypeKretprobe EBPFProgType = "Kretprobe" + + // ProgTypeUprobe refers to the Uprobe program type. + ProgTypeUprobe EBPFProgType = "Uprobe" + + // ProgTypeUretprobe refers to the Uretprobe program type. + ProgTypeUretprobe EBPFProgType = "Uretprobe" + + // ProgTypeTracepoint refers to the Tracepoint program type. + ProgTypeTracepoint EBPFProgType = "Tracepoint" +) + +// BpfApplicationProgram 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 == 'Fentry' ? has(self.fentry) : !has(self.fentry)",message="fentry configuration is required when type is Fentry, and forbidden otherwise" +// +kubebuilder:validation:XValidation:rule="has(self.type) && self.type == 'Fexit' ? has(self.fexit) : !has(self.fexit)",message="fexit configuration is required when type is Fexit, and forbidden otherwise" +// +kubebuilder:validation:XValidation:rule="has(self.type) && self.type == 'Kprobe' ? has(self.kprobe) : !has(self.kprobe)",message="kprobe configuration is required when type is Kprobe, and forbidden otherwise" +// +kubebuilder:validation:XValidation:rule="has(self.type) && self.type == 'Kretprobe' ? has(self.kretprobe) : !has(self.kretprobe)",message="kretprobe configuration is required when type is Kretprobe, 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" +// +kubebuilder:validation:XValidation:rule="has(self.type) && self.type == 'Tracepoint' ? has(self.tracepoint) : !has(self.tracepoint)",message="tracepoint configuration is required when type is Tracepoint, and forbidden otherwise" +type BpfApplicationProgram struct { + // Type specifies the bpf program type + // +unionDiscriminator + // +kubebuilder:validation:Required + // +kubebuilder:validation:Enum:="XDP";"TC";"TCX";"Fentry";"Fexit";"Kprobe";"Kretprobe";"Uprobe";"Uretprobe";"Tracepoint" + Type EBPFProgType `json:"type,omitempty"` + + // xdp defines the desired state of the application's XdpPrograms. + // +unionMember + // +optional + XDP *XdpProgramInfo `json:"xdp,omitempty"` + + // tc defines the desired state of the application's TcPrograms. + // +unionMember + // +optional + TC *TcProgramInfo `json:"tc,omitempty"` + + // tcx defines the desired state of the application's TcPrograms. + // +unionMember + // +optional + TCX *TcProgramInfo `json:"tcx,omitempty"` + + // fentry defines the desired state of the application's FentryPrograms. + // +unionMember + // +optional + Fentry *FentryProgramInfo `json:"fentry,omitempty"` + + // fexit defines the desired state of the application's FexitPrograms. + // +unionMember + // +optional + Fexit *FexitProgramInfo `json:"fexit,omitempty"` + + // kprobe defines the desired state of the application's KprobePrograms. + // +unionMember + // +optional + Kprobe *KprobeProgramInfo `json:"kprobe,omitempty"` + + // kretprobe defines the desired state of the application's KretprobePrograms. + // +unionMember + // +optional + Kretprobe *KprobeProgramInfo `json:"kretprobe,omitempty"` + + // uprobe defines the desired state of the application's UprobePrograms. + // +unionMember + // +optional + Uprobe *UprobeProgramInfo `json:"uprobe,omitempty"` + + // uretprobe defines the desired state of the application's UretprobePrograms. + // +unionMember + // +optional + Uretprobe *UprobeProgramInfo `json:"uretprobe,omitempty"` + + // tracepoint defines the desired state of the application's TracepointPrograms. + // +unionMember + // +optional + Tracepoint *TracepointProgramInfo `json:"tracepoint,omitempty"` +} + +// BpfApplicationSpec defines the desired state of BpfApplication +type BpfApplicationSpec 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 []BpfApplicationProgram `json:"programs,omitempty"` +} + +// BpfApplicationStatus defines the observed state of BpfApplication +type BpfApplicationStatus struct { + BpfProgramStatusCommon `json:",inline"` +} + +// +genclient +// +genclient:nonNamespaced +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Cluster + +// BpfApplication 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 BpfApplication struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BpfApplicationSpec `json:"spec,omitempty"` + Status BpfApplicationStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// BpfApplicationList contains a list of BpfApplications +type BpfApplicationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []BpfApplication `json:"items"` +} diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index 303fb614e..f2e712ba1 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -58,6 +58,169 @@ func (in *BpfAppCommon) DeepCopy() *BpfAppCommon { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BpfApplication) DeepCopyInto(out *BpfApplication) { + *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 BpfApplication. +func (in *BpfApplication) DeepCopy() *BpfApplication { + if in == nil { + return nil + } + out := new(BpfApplication) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BpfApplication) 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 *BpfApplicationList) DeepCopyInto(out *BpfApplicationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]BpfApplication, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BpfApplicationList. +func (in *BpfApplicationList) DeepCopy() *BpfApplicationList { + if in == nil { + return nil + } + out := new(BpfApplicationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BpfApplicationList) 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 *BpfApplicationProgram) DeepCopyInto(out *BpfApplicationProgram) { + *out = *in + if in.XDP != nil { + in, out := &in.XDP, &out.XDP + *out = new(XdpProgramInfo) + (*in).DeepCopyInto(*out) + } + if in.TC != nil { + in, out := &in.TC, &out.TC + *out = new(TcProgramInfo) + (*in).DeepCopyInto(*out) + } + if in.TCX != nil { + in, out := &in.TCX, &out.TCX + *out = new(TcProgramInfo) + (*in).DeepCopyInto(*out) + } + if in.Fentry != nil { + in, out := &in.Fentry, &out.Fentry + *out = new(FentryProgramInfo) + (*in).DeepCopyInto(*out) + } + if in.Fexit != nil { + in, out := &in.Fexit, &out.Fexit + *out = new(FexitProgramInfo) + (*in).DeepCopyInto(*out) + } + if in.Kprobe != nil { + in, out := &in.Kprobe, &out.Kprobe + *out = new(KprobeProgramInfo) + (*in).DeepCopyInto(*out) + } + if in.Kretprobe != nil { + in, out := &in.Kretprobe, &out.Kretprobe + *out = new(KprobeProgramInfo) + (*in).DeepCopyInto(*out) + } + if in.Uprobe != nil { + in, out := &in.Uprobe, &out.Uprobe + *out = new(UprobeProgramInfo) + (*in).DeepCopyInto(*out) + } + if in.Uretprobe != nil { + in, out := &in.Uretprobe, &out.Uretprobe + *out = new(UprobeProgramInfo) + (*in).DeepCopyInto(*out) + } + if in.Tracepoint != nil { + in, out := &in.Tracepoint, &out.Tracepoint + *out = new(TracepointProgramInfo) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BpfApplicationProgram. +func (in *BpfApplicationProgram) DeepCopy() *BpfApplicationProgram { + if in == nil { + return nil + } + out := new(BpfApplicationProgram) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BpfApplicationSpec) DeepCopyInto(out *BpfApplicationSpec) { + *out = *in + in.BpfAppCommon.DeepCopyInto(&out.BpfAppCommon) + if in.Programs != nil { + in, out := &in.Programs, &out.Programs + *out = make([]BpfApplicationProgram, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BpfApplicationSpec. +func (in *BpfApplicationSpec) DeepCopy() *BpfApplicationSpec { + if in == nil { + return nil + } + out := new(BpfApplicationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BpfApplicationStatus) DeepCopyInto(out *BpfApplicationStatus) { + *out = *in + in.BpfProgramStatusCommon.DeepCopyInto(&out.BpfProgramStatusCommon) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BpfApplicationStatus. +func (in *BpfApplicationStatus) DeepCopy() *BpfApplicationStatus { + if in == nil { + return nil + } + out := new(BpfApplicationStatus) + in.DeepCopyInto(out) + return out +} + // 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 diff --git a/apis/v1alpha1/zz_generated.register.go b/apis/v1alpha1/zz_generated.register.go index aaee37453..3d3ad9e37 100644 --- a/apis/v1alpha1/zz_generated.register.go +++ b/apis/v1alpha1/zz_generated.register.go @@ -61,6 +61,8 @@ func init() { // Adds the list of known types to Scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, + &BpfApplication{}, + &BpfApplicationList{}, &BpfProgram{}, &BpfProgramList{}, &FentryProgram{}, 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 d3046c6b3..a051e457d 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 @@ -4,6 +4,20 @@ metadata: creationTimestamp: null name: bpfman-agent-role rules: +- apiGroups: + - bpfman.io + resources: + - bpfapplications + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - bpfapplications/finalizers + verbs: + - update - apiGroups: - bpfman.io resources: diff --git a/bundle/manifests/bpfman-operator.clusterserviceversion.yaml b/bundle/manifests/bpfman-operator.clusterserviceversion.yaml index 726b39d86..28b29f3a0 100644 --- a/bundle/manifests/bpfman-operator.clusterserviceversion.yaml +++ b/bundle/manifests/bpfman-operator.clusterserviceversion.yaml @@ -4,6 +4,86 @@ metadata: annotations: alm-examples: |- [ + { + "apiVersion": "bpfman.io/v1alpha1", + "kind": "BpfApplication", + "metadata": { + "labels": { + "app.kubernetes.io/name": "bpfapplication" + }, + "name": "bpfapplication-sample" + }, + "spec": { + "bytecode": { + "image": { + "url": "quay.io/bpfman-bytecode/go-application-counter:latest" + } + }, + "nodeselector": {}, + "programs": [ + { + "kprobe": { + "bpffunctionname": "kprobe_counter", + "func_name": "try_to_wake_up", + "offset": 0, + "retprobe": false + }, + "type": "Kprobe" + }, + { + "tracepoint": { + "bpffunctionname": "tracepoint_kill_recorder", + "names": [ + "syscalls/sys_enter_kill" + ] + }, + "type": "Tracepoint" + }, + { + "tc": { + "bpffunctionname": "stats", + "direction": "ingress", + "interfaceselector": { + "primarynodeinterface": true + }, + "priority": 55 + }, + "type": "TC" + }, + { + "type": "Uprobe", + "uprobe": { + "bpffunctionname": "uprobe_counter", + "containers": { + "containernames": [ + "bpfman", + "bpfman-agent" + ], + "namespace": "bpfman", + "pods": { + "matchLabels": { + "name": "bpfman-daemon" + } + } + }, + "func_name": "malloc", + "retprobe": false, + "target": "libc" + } + }, + { + "type": "XDP", + "xdp": { + "bpffunctionname": "xdp_stats", + "interfaceselector": { + "primarynodeinterface": true + }, + "priority": 55 + } + } + ] + } + }, { "apiVersion": "bpfman.io/v1alpha1", "kind": "FentryProgram", @@ -216,7 +296,7 @@ metadata: capabilities: Basic Install categories: OpenShift Optional containerImage: quay.io/bpfman/bpfman-operator:v0.0.0 - createdAt: "2024-06-07T18:48:53Z" + createdAt: "2024-06-20T14:00:22Z" operatorframework.io/suggested-namespace-template: |- { "apiVersion": "v1", @@ -242,6 +322,9 @@ spec: apiservicedefinitions: {} customresourcedefinitions: owned: + - kind: BpfApplication + name: bpfapplications.bpfman.io + version: v1alpha1 - description: BpfProgram is the Schema for the BpfProgram API displayName: Bpf Program kind: BpfProgram @@ -616,6 +699,32 @@ spec: - patch - update - watch + - apiGroups: + - bpfman.io + resources: + - bpfapplications + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - bpfapplications/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - bpfapplications/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: diff --git a/bundle/manifests/bpfman.io_bpfapplications.yaml b/bundle/manifests/bpfman.io_bpfapplications.yaml new file mode 100644 index 000000000..df54bbf6c --- /dev/null +++ b/bundle/manifests/bpfman.io_bpfapplications.yaml @@ -0,0 +1,1342 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + creationTimestamp: null + name: bpfapplications.bpfman.io +spec: + group: bpfman.io + names: + kind: BpfApplication + listKind: BpfApplicationList + plural: bpfapplications + singular: bpfapplication + scope: Cluster + 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: BpfApplication 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: BpfApplicationProgram defines the desired state of + BpfApplication + properties: + fentry: + description: fentry defines the desired state of the application's + FentryPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + func_name: + description: Function to attach the fentry 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 + required: + - bpffunctionname + - func_name + type: object + fexit: + description: fexit defines the desired state of the application's + FexitPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + func_name: + description: Function to attach the fexit 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 + required: + - bpffunctionname + - func_name + type: object + kprobe: + description: kprobe defines the desired state of the application's + KprobePrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + func_name: + description: Functions to attach the kprobe 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 kprobe. + Not allowed for kretprobes. + format: int64 + type: integer + retprobe: + default: false + description: Whether the program is a kretprobe. Default + is false + type: boolean + required: + - bpffunctionname + - func_name + type: object + kretprobe: + description: kretprobe defines the desired state of the application's + KretprobePrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + func_name: + description: Functions to attach the kprobe 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 kprobe. + Not allowed for kretprobes. + format: int64 + type: integer + retprobe: + default: false + description: Whether the program is a kretprobe. Default + is false + type: boolean + required: + - bpffunctionname + - func_name + type: object + tc: + description: tc defines the desired state of the application's + TcPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + 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 + - direction + - interfaceselector + - priority + type: object + tcx: + description: tcx defines the desired state of the application's + TcPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + 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 + - direction + - interfaceselector + - priority + type: object + tracepoint: + description: tracepoint defines the desired state of the application's + TracepointPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + 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 + names: + description: |- + Names refers to the names of kernel tracepoints to attach the + bpf program to. + items: + type: string + type: array + required: + - bpffunctionname + - names + type: object + type: + description: Type specifies the bpf program type + enum: + - XDP + - TC + - TCX + - Fentry + - Fexit + - Kprobe + - Kretprobe + - Uprobe + - Uretprobe + - Tracepoint + type: string + uprobe: + description: uprobe defines the desired state of the application's + UprobePrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifes 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 + 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 + namespace: + default: "" + description: Target namespaces. + type: string + 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 + - target + type: object + uretprobe: + description: uretprobe defines the desired state of the application's + UretprobePrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifes 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 + 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 + namespace: + default: "" + description: Target namespaces. + type: string + 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 + - target + type: object + xdp: + description: xdp defines the desired state of the application's + XdpPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + 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 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 + items: + enum: + - aborted + - drop + - pass + - tx + - redirect + - dispatcher_return + type: string + maxItems: 6 + type: array + required: + - bpffunctionname + - 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: fentry configuration is required when type is Fentry, + and forbidden otherwise + rule: 'has(self.type) && self.type == ''Fentry'' ? has(self.fentry) + : !has(self.fentry)' + - message: fexit configuration is required when type is Fexit, and + forbidden otherwise + rule: 'has(self.type) && self.type == ''Fexit'' ? has(self.fexit) + : !has(self.fexit)' + - message: kprobe configuration is required when type is Kprobe, + and forbidden otherwise + rule: 'has(self.type) && self.type == ''Kprobe'' ? has(self.kprobe) + : !has(self.kprobe)' + - message: kretprobe configuration is required when type is Kretprobe, + and forbidden otherwise + rule: 'has(self.type) && self.type == ''Kretprobe'' ? has(self.kretprobe) + : !has(self.kretprobe)' + - 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)' + - message: tracepoint configuration is required when type is Tracepoint, + and forbidden otherwise + rule: 'has(self.type) && self.type == ''Tracepoint'' ? has(self.tracepoint) + : !has(self.tracepoint)' + 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/cmd/bpfman-agent/main.go b/cmd/bpfman-agent/main.go index 9d63bac18..c99630adc 100644 --- a/cmd/bpfman-agent/main.go +++ b/cmd/bpfman-agent/main.go @@ -193,6 +193,13 @@ func main() { os.Exit(1) } + if err = (&bpfmanagent.BpfApplicationReconciler{ + ReconcilerCommon: common, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create BpfApplicationProgram controller", "controller", "BpfProgram") + 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 ba1e8dc93..6d2f3323b 100644 --- a/cmd/bpfman-operator/main.go +++ b/cmd/bpfman-operator/main.go @@ -231,6 +231,12 @@ func main() { os.Exit(1) } + if err = (&bpfmanoperator.BpfApplicationReconciler{ + ReconcilerCommon: common, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "BpfApplication") + 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 new file mode 100644 index 000000000..845d51f86 --- /dev/null +++ b/config/crd/bases/bpfman.io_bpfapplications.yaml @@ -0,0 +1,1336 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: bpfapplications.bpfman.io +spec: + group: bpfman.io + names: + kind: BpfApplication + listKind: BpfApplicationList + plural: bpfapplications + singular: bpfapplication + scope: Cluster + 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: BpfApplication 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: BpfApplicationProgram defines the desired state of + BpfApplication + properties: + fentry: + description: fentry defines the desired state of the application's + FentryPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + func_name: + description: Function to attach the fentry 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 + required: + - bpffunctionname + - func_name + type: object + fexit: + description: fexit defines the desired state of the application's + FexitPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + func_name: + description: Function to attach the fexit 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 + required: + - bpffunctionname + - func_name + type: object + kprobe: + description: kprobe defines the desired state of the application's + KprobePrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + func_name: + description: Functions to attach the kprobe 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 kprobe. + Not allowed for kretprobes. + format: int64 + type: integer + retprobe: + default: false + description: Whether the program is a kretprobe. Default + is false + type: boolean + required: + - bpffunctionname + - func_name + type: object + kretprobe: + description: kretprobe defines the desired state of the application's + KretprobePrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + func_name: + description: Functions to attach the kprobe 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 kprobe. + Not allowed for kretprobes. + format: int64 + type: integer + retprobe: + default: false + description: Whether the program is a kretprobe. Default + is false + type: boolean + required: + - bpffunctionname + - func_name + type: object + tc: + description: tc defines the desired state of the application's + TcPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + 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 + - direction + - interfaceselector + - priority + type: object + tcx: + description: tcx defines the desired state of the application's + TcPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + 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 + - direction + - interfaceselector + - priority + type: object + tracepoint: + description: tracepoint defines the desired state of the application's + TracepointPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + 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 + names: + description: |- + Names refers to the names of kernel tracepoints to attach the + bpf program to. + items: + type: string + type: array + required: + - bpffunctionname + - names + type: object + type: + description: Type specifies the bpf program type + enum: + - XDP + - TC + - TCX + - Fentry + - Fexit + - Kprobe + - Kretprobe + - Uprobe + - Uretprobe + - Tracepoint + type: string + uprobe: + description: uprobe defines the desired state of the application's + UprobePrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifes 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 + 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 + namespace: + default: "" + description: Target namespaces. + type: string + 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 + - target + type: object + uretprobe: + description: uretprobe defines the desired state of the application's + UretprobePrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifes 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 + 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 + namespace: + default: "" + description: Target namespaces. + type: string + 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 + - target + type: object + xdp: + description: xdp defines the desired state of the application's + XdpPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + 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 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 + items: + enum: + - aborted + - drop + - pass + - tx + - redirect + - dispatcher_return + type: string + maxItems: 6 + type: array + required: + - bpffunctionname + - 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: fentry configuration is required when type is Fentry, + and forbidden otherwise + rule: 'has(self.type) && self.type == ''Fentry'' ? has(self.fentry) + : !has(self.fentry)' + - message: fexit configuration is required when type is Fexit, and + forbidden otherwise + rule: 'has(self.type) && self.type == ''Fexit'' ? has(self.fexit) + : !has(self.fexit)' + - message: kprobe configuration is required when type is Kprobe, + and forbidden otherwise + rule: 'has(self.type) && self.type == ''Kprobe'' ? has(self.kprobe) + : !has(self.kprobe)' + - message: kretprobe configuration is required when type is Kretprobe, + and forbidden otherwise + rule: 'has(self.type) && self.type == ''Kretprobe'' ? has(self.kretprobe) + : !has(self.kretprobe)' + - 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)' + - message: tracepoint configuration is required when type is Tracepoint, + and forbidden otherwise + rule: 'has(self.type) && self.type == ''Tracepoint'' ? has(self.tracepoint) + : !has(self.tracepoint)' + 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/kustomization.yaml b/config/crd/kustomization.yaml index ba17a6bcc..e70f0d3ea 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -10,7 +10,7 @@ resources: - bases/bpfman.io_uprobeprograms.yaml - bases/bpfman.io_fentryprograms.yaml - bases/bpfman.io_fexitprograms.yaml - + - bases/bpfman.io_bpfapplications.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -24,6 +24,7 @@ patchesStrategicMerge: #- patches/webhook_in_uprobeprograms.yaml #- patches/webhook_in_fentryprograms.yaml #- patches/webhook_in_fexitprograms.yaml +#- patches/webhook_in_bpfapplications.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -35,7 +36,8 @@ patchesStrategicMerge: #- patches/cainjection_in_kprobeprograms.yaml #- patches/cainjection_in_uprobeprograms.yaml #- patches/cainjection_in_fentryprograms.yaml -#- patches/cainjection_in_fentryprograms.yaml +#- patches/cainjection_in_fexitrograms.yaml +#- patches/cainjection_in_bpfapplications.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_bpfapplications.yaml b/config/crd/patches/cainjection_in_bpfapplications.yaml new file mode 100644 index 000000000..b282ec14e --- /dev/null +++ b/config/crd/patches/cainjection_in_bpfapplications.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: bpfapplications.bpfman.io diff --git a/config/crd/patches/webhook_in_bpfapplications.yaml b/config/crd/patches/webhook_in_bpfapplications.yaml new file mode 100644 index 000000000..9c0c7f2f5 --- /dev/null +++ b/config/crd/patches/webhook_in_bpfapplications.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: bpfapplications.bpfman.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/rbac/bpfapplication_editor_role.yaml b/config/rbac/bpfapplication_editor_role.yaml new file mode 100644 index 000000000..b803892b2 --- /dev/null +++ b/config/rbac/bpfapplication_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit bpfapplications. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: bpfapplication-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: bpfapplication-editor-role +rules: +- apiGroups: + - bpfman.io + resources: + - bpfapplications + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - bpfapplications/status + verbs: + - get diff --git a/config/rbac/bpfapplication_viewer_role.yaml b/config/rbac/bpfapplication_viewer_role.yaml new file mode 100644 index 000000000..81121592d --- /dev/null +++ b/config/rbac/bpfapplication_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view bpfapplications. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: bpfapplication-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: bpfapplication-viewer-role +rules: +- apiGroups: + - bpfman.io + resources: + - bpfapplications + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - bpfapplications/status + verbs: + - get diff --git a/config/rbac/bpfman-agent/role.yaml b/config/rbac/bpfman-agent/role.yaml index 23d4e9d68..4cc0e9fe9 100644 --- a/config/rbac/bpfman-agent/role.yaml +++ b/config/rbac/bpfman-agent/role.yaml @@ -4,6 +4,20 @@ kind: ClusterRole metadata: name: agent-role rules: +- apiGroups: + - bpfman.io + resources: + - bpfapplications + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - bpfapplications/finalizers + verbs: + - update - apiGroups: - bpfman.io resources: diff --git a/config/rbac/bpfman-operator/role.yaml b/config/rbac/bpfman-operator/role.yaml index 629fc6bb9..3a6848ce5 100644 --- a/config/rbac/bpfman-operator/role.yaml +++ b/config/rbac/bpfman-operator/role.yaml @@ -16,6 +16,32 @@ rules: - patch - update - watch +- apiGroups: + - bpfman.io + resources: + - bpfapplications + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - bpfapplications/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - bpfapplications/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: diff --git a/config/samples/bpfman.io_v1alpha1_bpfapplication.yaml b/config/samples/bpfman.io_v1alpha1_bpfapplication.yaml new file mode 100644 index 000000000..bce82eac0 --- /dev/null +++ b/config/samples/bpfman.io_v1alpha1_bpfapplication.yaml @@ -0,0 +1,51 @@ +apiVersion: bpfman.io/v1alpha1 +kind: BpfApplication +metadata: + labels: + app.kubernetes.io/name: bpfapplication + name: bpfapplication-sample +spec: + # Select all nodes + nodeselector: {} + bytecode: + image: + url: quay.io/bpfman-bytecode/go-application-counter:latest + programs: + - type: Kprobe + kprobe: + bpffunctionname: kprobe_counter + func_name: try_to_wake_up + offset: 0 + retprobe: false + - type: Tracepoint + tracepoint: + bpffunctionname: tracepoint_kill_recorder + names: + - syscalls/sys_enter_kill + - type: TC + tc: + bpffunctionname: stats + interfaceselector: + primarynodeinterface: true + priority: 55 + direction: ingress + - type: Uprobe + uprobe: + bpffunctionname: uprobe_counter + func_name: malloc + target: libc + retprobe: false + containers: + namespace: bpfman + pods: + matchLabels: + name: bpfman-daemon + containernames: + - bpfman + - bpfman-agent + - type: XDP + xdp: + bpffunctionname: xdp_stats + interfaceselector: + primarynodeinterface: true + priority: 55 diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index e6f2d9428..4795266f4 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -7,4 +7,5 @@ resources: - bpfman.io_v1alpha1_uprobe_uprobeprogram.yaml - bpfman.io_v1alpha1_fentry_fentryprogram.yaml - bpfman.io_v1alpha1_fexit_fexitprogram.yaml + - bpfman.io_v1alpha1_bpfapplication.yaml # +kubebuilder:scaffold:manifestskustomizesamples diff --git a/controllers/bpfman-agent/application-program.go b/controllers/bpfman-agent/application-program.go new file mode 100644 index 000000000..32a3f6a2e --- /dev/null +++ b/controllers/bpfman-agent/application-program.go @@ -0,0 +1,284 @@ +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/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfapplications,verbs=get;list;watch + +type BpfApplicationReconciler struct { + ReconcilerCommon + currentApp *bpfmaniov1alpha1.BpfApplication + ourNode *v1.Node +} + +func (r *BpfApplicationReconciler) getRecType() string { + return internal.ApplicationString +} + +func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // Initialize node and current program + r.currentApp = &bpfmaniov1alpha1.BpfApplication{} + r.ourNode = &v1.Node{} + r.Logger = ctrl.Log.WithName("application") + r.appOwner = &bpfmaniov1alpha1.BpfApplication{} + r.finalizer = internal.BpfApplicationControllerFinalizer + r.recType = internal.ApplicationString + + ctxLogger := log.FromContext(ctx) + ctxLogger.Info("Reconcile Application: Enter", "ReconcileKey", req) + + // 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.BpfApplicationList{} + + opts := []client.ListOption{} + + if err := r.List(ctx, appPrograms, opts...); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting BpfApplicationPrograms for full reconcile %s : %v", + req.NamespacedName, err) + } + + if len(appPrograms.Items) == 0 { + r.Logger.Info("BpfApplicationController found no application Programs") + return ctrl.Result{Requeue: false}, nil + } + + var res ctrl.Result + var err error + var complete bool + + namePrefix := func( + app bpfmaniov1alpha1.BpfApplication, + prog bpfmaniov1alpha1.BpfApplicationProgram) string { + return app.Name + "-" + strings.ToLower(string(prog.Type)) + "-" + } + + for i, a := range appPrograms.Items { + for j, p := range a.Spec.Programs { + switch p.Type { + case bpfmaniov1alpha1.ProgTypeFentry: + fentryProgram := bpfmaniov1alpha1.FentryProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: namePrefix(a, p) + sanitize(p.Fentry.FunctionName), + }, + Spec: bpfmaniov1alpha1.FentryProgramSpec{ + FentryProgramInfo: *p.Fentry, + BpfAppCommon: a.Spec.BpfAppCommon, + }, + } + rec := &FentryProgramReconciler{ + ReconcilerCommon: r.ReconcilerCommon, + currentFentryProgram: &fentryProgram, + ourNode: r.ourNode, + } + rec.appOwner = &a + fentryObjects := []client.Object{&fentryProgram} + // Reconcile FentryProgram. + complete, res, err = r.reconcileCommon(ctx, rec, fentryObjects) + + case bpfmaniov1alpha1.ProgTypeFexit: + fexitProgram := bpfmaniov1alpha1.FexitProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: namePrefix(a, p) + sanitize(p.Fexit.FunctionName), + }, + Spec: bpfmaniov1alpha1.FexitProgramSpec{ + FexitProgramInfo: *p.Fexit, + BpfAppCommon: a.Spec.BpfAppCommon, + }, + } + rec := &FexitProgramReconciler{ + ReconcilerCommon: r.ReconcilerCommon, + currentFexitProgram: &fexitProgram, + ourNode: r.ourNode, + } + rec.appOwner = &a + fexitObjects := []client.Object{&fexitProgram} + // Reconcile FexitProgram. + complete, res, err = r.reconcileCommon(ctx, rec, fexitObjects) + + case bpfmaniov1alpha1.ProgTypeKprobe, + bpfmaniov1alpha1.ProgTypeKretprobe: + kprobeProgram := bpfmaniov1alpha1.KprobeProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: namePrefix(a, p) + sanitize(p.Kprobe.FunctionName), + }, + Spec: bpfmaniov1alpha1.KprobeProgramSpec{ + KprobeProgramInfo: *p.Kprobe, + BpfAppCommon: a.Spec.BpfAppCommon, + }, + } + rec := &KprobeProgramReconciler{ + ReconcilerCommon: r.ReconcilerCommon, + currentKprobeProgram: &kprobeProgram, + ourNode: r.ourNode, + } + rec.appOwner = &a + kprobeObjects := []client.Object{&kprobeProgram} + // Reconcile KprobeProgram or KpretprobeProgram. + complete, res, err = r.reconcileCommon(ctx, rec, kprobeObjects) + + case bpfmaniov1alpha1.ProgTypeUprobe, + bpfmaniov1alpha1.ProgTypeUretprobe: + uprobeProgram := bpfmaniov1alpha1.UprobeProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: namePrefix(a, p) + sanitize(p.Uprobe.FunctionName), + }, + Spec: bpfmaniov1alpha1.UprobeProgramSpec{ + UprobeProgramInfo: *p.Uprobe, + BpfAppCommon: a.Spec.BpfAppCommon, + }, + } + rec := &UprobeProgramReconciler{ + ReconcilerCommon: r.ReconcilerCommon, + currentUprobeProgram: &uprobeProgram, + ourNode: r.ourNode, + } + rec.appOwner = &a + uprobeObjects := []client.Object{&uprobeProgram} + // Reconcile UprobeProgram or UpretprobeProgram. + complete, res, err = r.reconcileCommon(ctx, rec, uprobeObjects) + + case bpfmaniov1alpha1.ProgTypeTracepoint: + tracepointProgram := bpfmaniov1alpha1.TracepointProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: namePrefix(a, p) + sanitize(p.Tracepoint.Names[0]), + }, + Spec: bpfmaniov1alpha1.TracepointProgramSpec{ + TracepointProgramInfo: *p.Tracepoint, + BpfAppCommon: a.Spec.BpfAppCommon, + }, + } + rec := &TracepointProgramReconciler{ + ReconcilerCommon: r.ReconcilerCommon, + currentTracepointProgram: &tracepointProgram, + ourNode: r.ourNode, + } + rec.appOwner = &a + tracepointObjects := []client.Object{&tracepointProgram} + // Reconcile TracepointProgram. + complete, res, err = r.reconcileCommon(ctx, rec, tracepointObjects) + + case bpfmaniov1alpha1.ProgTypeTC, + bpfmaniov1alpha1.ProgTypeTCX: + interfaces, ifErr := getInterfaces(&p.TC.InterfaceSelector, r.ourNode) + if ifErr != nil { + ctxLogger.Error(ifErr, "failed to get interfaces for TC Program", + "app program name", a.Name, "program index", j) + continue + } + tcProgram := bpfmaniov1alpha1.TcProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: namePrefix(a, p) + p.TC.Direction + "-" + interfaces[0], + }, + Spec: bpfmaniov1alpha1.TcProgramSpec{ + TcProgramInfo: *p.TC, + BpfAppCommon: a.Spec.BpfAppCommon, + }, + } + rec := &TcProgramReconciler{ + ReconcilerCommon: r.ReconcilerCommon, + currentTcProgram: &tcProgram, + ourNode: r.ourNode, + } + rec.appOwner = &a + tcObjects := []client.Object{&tcProgram} + // Reconcile TcProgram. + complete, res, err = r.reconcileCommon(ctx, rec, tcObjects) + + case bpfmaniov1alpha1.ProgTypeXDP: + interfaces, ifErr := getInterfaces(&p.XDP.InterfaceSelector, r.ourNode) + if ifErr != nil { + ctxLogger.Error(ifErr, "failed to get interfaces for XDP Program", + "app program name", a.Name, "program index", j) + continue + } + xdpProgram := bpfmaniov1alpha1.XdpProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: namePrefix(a, p) + interfaces[0], + }, + Spec: bpfmaniov1alpha1.XdpProgramSpec{ + XdpProgramInfo: *p.XDP, + BpfAppCommon: a.Spec.BpfAppCommon, + }, + } + rec := &XdpProgramReconciler{ + ReconcilerCommon: r.ReconcilerCommon, + currentXdpProgram: &xdpProgram, + ourNode: r.ourNode, + } + rec.appOwner = &a + xdpObjects := []client.Object{&xdpProgram} + // Reconcile XdpProgram. + complete, res, err = r.reconcileCommon(ctx, rec, xdpObjects) + + default: + r.Logger.Error(fmt.Errorf("unsupported bpf program type"), "unsupported bpf 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 { + // 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 BpfApplication object is updated, +// load the programs to the node via bpfman, and then create a bpfProgram object +// to reflect per node state information. +func (r *BpfApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.BpfApplication{}, builder.WithPredicates(predicate.And(predicate.GenerationChangedPredicate{}, predicate.ResourceVersionChangedPredicate{}))). + Owns(&bpfmaniov1alpha1.BpfProgram{}, + builder.WithPredicates(predicate.And( + internal.BpfProgramTypePredicate(internal.ApplicationString), + internal.BpfProgramNodePredicate(r.NodeName)), + ), + ). + // Only trigger reconciliation if node labels change since that could + // make the BpfApplication 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))), + ). + Complete(r) +} diff --git a/controllers/bpfman-agent/application-program_test.go b/controllers/bpfman-agent/application-program_test.go new file mode 100644 index 000000000..9b5f1d211 --- /dev/null +++ b/controllers/bpfman-agent/application-program_test.go @@ -0,0 +1,305 @@ +package bpfmanagent + +import ( + "context" + "fmt" + "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" + 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 TestBpfApplicationControllerCreate(t *testing.T) { + var ( + // global config + name = "fakeAppProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + fakeNode = testutils.NewNode("fake-control-plane") + ctx = context.TODO() + // fentry program config + bpfFentryFunctionName = "fentry_test" + fentryFunctionName = "do_unlinkat" + fentryBpfProgName = fmt.Sprintf("%s-%s-%s-%s-%s", name, "fentry", "do-unlinkat", fakeNode.Name, "do-unlinkat") + fentryBpfProg = &bpfmaniov1alpha1.BpfProgram{} + fentryFakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81" + // kprobe program config + bpfKprobeFunctionName = "kprobe_test" + kprobeFunctionName = "try_to_wake_up" + kprobeBpfProgName = fmt.Sprintf("%s-%s-%s-%s-%s", name, "kprobe", "try-to-wake-up", fakeNode.Name, "try-to-wake-up") + kprobeBpfProg = &bpfmaniov1alpha1.BpfProgram{} + kprobeFakeUID = "ef71d42c-aa21-48e8-a697-82391d801a82" + kprobeOffset = 0 + kprobeRetprobe = false + kprobecontainerpid int32 = 0 + ) + + // A AppProgram object with metadata and spec. + App := &bpfmaniov1alpha1.BpfApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: bpfmaniov1alpha1.BpfApplicationSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + Programs: []bpfmaniov1alpha1.BpfApplicationProgram{ + { + Type: bpfmaniov1alpha1.ProgTypeFentry, + Fentry: &bpfmaniov1alpha1.FentryProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFentryFunctionName, + }, + FunctionName: fentryFunctionName, + }, + }, + { + Type: bpfmaniov1alpha1.ProgTypeKprobe, + Kprobe: &bpfmaniov1alpha1.KprobeProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfKprobeFunctionName, + }, + FunctionName: kprobeFunctionName, + Offset: uint64(kprobeOffset), + RetProbe: kprobeRetprobe, + }, + }, + }, + }, + } + + // 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.BpfApplicationList{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfApplication{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgramList{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgram{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(App).WithStatusSubresource(&bpfmaniov1alpha1.BpfProgram{}).WithRuntimeObjects(objs...).Build() + + cli := agenttestutils.NewBpfmanClientFake() + + rc := ReconcilerCommon{ + Client: cl, + Scheme: s, + BpfmanClient: cli, + NodeName: fakeNode.Name, + appOwner: App, + } + + // 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} + + // 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 = cl.Get(ctx, types.NamespacedName{Name: fentryBpfProgName, Namespace: metav1.NamespaceAll}, fentryBpfProg) + require.NoError(t, err) + + require.NotEmpty(t, fentryBpfProg) + // Finalizer is written + require.Equal(t, internal.BpfApplicationControllerFinalizer, fentryBpfProg.Finalizers[0]) + // owningConfig Label was correctly set + require.Equal(t, name, fentryBpfProg.Labels[internal.BpfProgramOwnerLabel]) + // node Label was correctly set + require.Equal(t, fakeNode.Name, fentryBpfProg.Labels[internal.K8sHostLabel]) + // fentry function Annotation was correctly set + require.Equal(t, fentryFunctionName, fentryBpfProg.Annotations[internal.FentryProgramFunction]) + // Type is set + require.Equal(t, r.getRecType(), fentryBpfProg.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of bpfProgram with Fake UID since the fake API server won't + fentryBpfProg.UID = types.UID(fentryFakeUID) + err = cl.Update(ctx, fentryBpfProg) + 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) + } + + // Require no requeue + require.False(t, res.Requeue) + + // do Fentry Program + expectedLoadReq := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFentryFunctionName, + ProgramType: *internal.Tracing.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(fentryBpfProg.UID), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_FentryAttachInfo{ + FentryAttachInfo: &gobpfman.FentryAttachInfo{ + FnName: fentryFunctionName, + }, + }, + }, + } + + // Check that the bpfProgram's programs was correctly updated + err = cl.Get(ctx, types.NamespacedName{Name: fentryBpfProgName, Namespace: metav1.NamespaceAll}, fentryBpfProg) + require.NoError(t, err) + + // prog ID should already have been set + id, err := bpfmanagentinternal.GetID(fentryBpfProg) + 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 = cl.Get(ctx, types.NamespacedName{Name: fentryBpfProgName, Namespace: metav1.NamespaceAll}, fentryBpfProg) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), fentryBpfProg.Status.Conditions[0].Type) + + // do kprobe program + // First reconcile should create the bpf program object + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + err = cl.Get(ctx, types.NamespacedName{Name: kprobeBpfProgName, Namespace: metav1.NamespaceAll}, kprobeBpfProg) + require.NoError(t, err) + + require.NotEmpty(t, kprobeBpfProg) + // Finalizer is written + require.Equal(t, internal.BpfApplicationControllerFinalizer, kprobeBpfProg.Finalizers[0]) + // owningConfig Label was correctly set + require.Equal(t, name, kprobeBpfProg.Labels[internal.BpfProgramOwnerLabel]) + // node Label was correctly set + require.Equal(t, fakeNode.Name, kprobeBpfProg.Labels[internal.K8sHostLabel]) + // fentry function Annotation was correctly set + require.Equal(t, kprobeFunctionName, kprobeBpfProg.Annotations[internal.KprobeProgramFunction]) + // Type is set + require.Equal(t, r.getRecType(), kprobeBpfProg.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of bpfProgram with Fake UID since the fake API server won't + kprobeBpfProg.UID = types.UID(kprobeFakeUID) + err = cl.Update(ctx, kprobeBpfProg) + 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) + } + + // Require no requeue + require.False(t, res.Requeue) + + expectedLoadReq = &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfKprobeFunctionName, + ProgramType: *internal.Kprobe.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(kprobeBpfProg.UID), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_KprobeAttachInfo{ + KprobeAttachInfo: &gobpfman.KprobeAttachInfo{ + FnName: kprobeFunctionName, + Offset: uint64(kprobeOffset), + Retprobe: kprobeRetprobe, + ContainerPid: &kprobecontainerpid, + }, + }, + }, + } + + // Check that the bpfProgram's programs was correctly updated + err = cl.Get(ctx, types.NamespacedName{Name: kprobeBpfProgName, Namespace: metav1.NamespaceAll}, kprobeBpfProg) + require.NoError(t, err) + + // prog ID should already have been set + id, err = bpfmanagentinternal.GetID(kprobeBpfProg) + 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 = cl.Get(ctx, types.NamespacedName{Name: kprobeBpfProgName, Namespace: metav1.NamespaceAll}, 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 6485dd417..0f0a50937 100644 --- a/controllers/bpfman-agent/common.go +++ b/controllers/bpfman-agent/common.go @@ -21,6 +21,7 @@ import ( "fmt" "reflect" "strconv" + "strings" "time" v1 "k8s.io/api/core/v1" @@ -54,6 +55,7 @@ import ( //+kubebuilder:rbac:groups=bpfman.io,resources=uprobeprograms/finalizers,verbs=update //+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 @@ -71,6 +73,9 @@ type ReconcilerCommon struct { Logger logr.Logger NodeName string progId *uint32 + finalizer string + recType string + appOwner metav1.Object // Set if the owner is an application } // bpfmanReconciler defines a generic bpfProgram K8s object reconciler which can @@ -88,6 +93,9 @@ type bpfmanReconciler interface { // getFinalizer returns the string used for the finalizer to prevent the // BpfProgram object from deletion until cleanup can be performed getFinalizer() string + // getOwner returns the owner of the BpfProgram object. This is either the + // *Program or the BpfApplicationProgram that created it. + getOwner() metav1.Object // getRecType returns the type of the reconciler. This is often the string // representation of the ProgramType, but in cases where there are multiple // reconcilers for a single ProgramType, it may be different (e.g., uprobe, @@ -119,13 +127,16 @@ type bpfmanReconciler interface { } // reconcileCommon is the common reconciler loop called by each bpfman -// reconciler. It reconciles each program in the list. reconcileCommon should -// not return error because it will trigger an infinite reconcile loop. -// Instead, it should report the error to 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 +// reconciler. It reconciles each program in the list. The boolean return +// value is set to true if we've made it through all the programs in the list +// without anything being updated and a requeue has not been requested. Otherwise, +// it's set to false. reconcileCommon should not return error because it will +// trigger an infinite reconcile loop. Instead, it should report the error to +// 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, - programs []client.Object) (ctrl.Result, error) { + programs []client.Object) (bool, ctrl.Result, error) { r.Logger.V(1).Info("Start reconcileCommon()") @@ -133,7 +144,7 @@ func (r *ReconcilerCommon) reconcileCommon(ctx context.Context, rec bpfmanReconc loadedBpfPrograms, err := bpfmanagentinternal.ListBpfmanPrograms(ctx, r.BpfmanClient, rec.getProgType()) if err != nil { r.Logger.Error(err, "failed to list loaded bpfman programs") - return ctrl.Result{Requeue: true, RequeueAfter: retryDurationAgent}, nil + return false, ctrl.Result{Requeue: true, RequeueAfter: retryDurationAgent}, nil } requeue := false // initialize requeue to false @@ -144,7 +155,7 @@ func (r *ReconcilerCommon) reconcileCommon(ctx context.Context, rec bpfmanReconc err := rec.setCurrentProgram(program) if err != nil { r.Logger.Error(err, "Failed to set current program") - return ctrl.Result{Requeue: true, RequeueAfter: retryDurationAgent}, nil + return false, ctrl.Result{Requeue: true, RequeueAfter: retryDurationAgent}, nil } result, err := r.reconcileProgram(ctx, rec, program, loadedBpfPrograms) @@ -157,7 +168,7 @@ func (r *ReconcilerCommon) reconcileCommon(ctx context.Context, rec bpfmanReconc // continue with next program case internal.Updated: // return - return ctrl.Result{Requeue: false}, nil + return false, ctrl.Result{Requeue: false}, nil case internal.Requeue: // remember to do a requeue when we're done and continue with next program requeue = true @@ -166,11 +177,11 @@ func (r *ReconcilerCommon) reconcileCommon(ctx context.Context, rec bpfmanReconc if requeue { // A requeue has been requested - return ctrl.Result{RequeueAfter: retryDurationAgent}, nil + return false, ctrl.Result{RequeueAfter: retryDurationAgent}, nil } else { // We've made it through all the programs in the list without anything being // updated and a reque has not been requested. - return ctrl.Result{Requeue: false}, nil + return true, ctrl.Result{Requeue: false}, nil } } @@ -209,9 +220,6 @@ func (r *ReconcilerCommon) reconcileBpfProgram(ctx context.Context, if err != nil { return bpfmaniov1alpha1.BpfProgCondBytecodeSelectorError, err } - - r.Logger.V(1).WithValues("loadRequest", loadRequest).WithValues("loadedBpfProgram", loadedBpfProgram).Info("StateMatch") - isSame, reasons := bpfmanagentinternal.DoesProgExist(loadedBpfProgram, loadRequest) if !isSame { r.Logger.V(1).Info("bpf program is in wrong state, unloading and reloading", "reason", reasons, "bpfProgram Name", bpfProgram.Name, "bpf program ID", id) @@ -491,13 +499,17 @@ func (r *ReconcilerCommon) updateStatus(ctx context.Context, bpfProgram *bpfmani } func (r *ReconcilerCommon) getExistingBpfPrograms(ctx context.Context, - program metav1.Object) (map[string]bpfmaniov1alpha1.BpfProgram, error) { + rec bpfmanReconciler) (map[string]bpfmaniov1alpha1.BpfProgram, error) { bpfProgramList := &bpfmaniov1alpha1.BpfProgramList{} // Only list bpfPrograms for this *Program and the controller's node opts := []client.ListOption{ - client.MatchingLabels{internal.BpfProgramOwnerLabel: program.GetName(), internal.K8sHostLabel: r.NodeName}, + client.MatchingLabels{ + internal.BpfProgramOwnerLabel: rec.getOwner().GetName(), + internal.BpfParentProgram: rec.getName(), + internal.K8sHostLabel: r.NodeName, + }, } err := r.List(ctx, bpfProgramList, opts...) @@ -517,26 +529,30 @@ func (r *ReconcilerCommon) getExistingBpfPrograms(ctx context.Context, // into a central location. func (r *ReconcilerCommon) createBpfProgram( bpfProgramName string, - finalizer string, - owner metav1.Object, - ownerType string, + rec bpfmanReconciler, annotations map[string]string) (*bpfmaniov1alpha1.BpfProgram, error) { + + r.Logger.V(1).Info("createBpfProgram()", "Name", bpfProgramName, + "Owner", rec.getOwner().GetName(), "OwnerType", rec.getRecType(), "Name", rec.getName()) + bpfProg := &bpfmaniov1alpha1.BpfProgram{ ObjectMeta: metav1.ObjectMeta{ Name: bpfProgramName, - Finalizers: []string{finalizer}, - Labels: map[string]string{internal.BpfProgramOwnerLabel: owner.GetName(), - internal.K8sHostLabel: r.NodeName}, + Finalizers: []string{rec.getFinalizer()}, + Labels: map[string]string{ + internal.BpfProgramOwnerLabel: rec.getOwner().GetName(), + internal.BpfParentProgram: rec.getName(), + internal.K8sHostLabel: r.NodeName}, Annotations: annotations, }, Spec: bpfmaniov1alpha1.BpfProgramSpec{ - Type: ownerType, + Type: rec.getRecType(), }, Status: bpfmaniov1alpha1.BpfProgramStatus{Conditions: []metav1.Condition{}}, } // Make the corresponding BpfProgramConfig the owner - if err := ctrl.SetControllerReference(owner, bpfProg, r.Scheme); err != nil { + if err := ctrl.SetControllerReference(rec.getOwner(), bpfProg, r.Scheme); err != nil { return nil, fmt.Errorf("failed to bpfProgram object owner reference: %v", err) } @@ -624,7 +640,6 @@ func (r *ReconcilerCommon) handleProgDelete( func (r *ReconcilerCommon) handleProgCreateOrUpdate( ctx context.Context, rec bpfmanReconciler, - program client.Object, existingBpfPrograms map[string]bpfmaniov1alpha1.BpfProgram, expectedBpfPrograms *bpfmaniov1alpha1.BpfProgramList, loadedBpfPrograms map[string]*gobpfman.ListResponse_ListResult, @@ -646,7 +661,7 @@ func (r *ReconcilerCommon) handleProgCreateOrUpdate( } else { // Create a new bpfProgram Object for this program. opts := client.CreateOptions{} - r.Logger.Info("Creating bpfProgram", "Name", expectedBpfProgram.Name, "Owner", program.GetName()) + r.Logger.Info("Creating bpfProgram", "Name", expectedBpfProgram.Name, "Owner", rec.getOwner().GetName()) if err := r.Create(ctx, &expectedBpfProgram, &opts); err != nil { return internal.Requeue, fmt.Errorf("failed to create bpfProgram object: %v", err) } @@ -738,11 +753,11 @@ func (r *ReconcilerCommon) reconcileProgram(ctx context.Context, return internal.Requeue, fmt.Errorf("failed to check if node is selected: %v", err) } - isBeingDeleted := !program.GetDeletionTimestamp().IsZero() + isBeingDeleted := !rec.getOwner().GetDeletionTimestamp().IsZero() // Query the K8s API to get a list of existing bpfPrograms for this *Program // on this node. - existingBpfPrograms, err := r.getExistingBpfPrograms(ctx, program) + existingBpfPrograms, err := r.getExistingBpfPrograms(ctx, rec) if err != nil { return internal.Requeue, fmt.Errorf("failed to get existing bpfPrograms: %v", err) } @@ -772,7 +787,7 @@ func (r *ReconcilerCommon) reconcileProgram(ctx context.Context, if err != nil { return internal.Requeue, fmt.Errorf("failed to get expected bpfPrograms: %v", err) } - return r.handleProgCreateOrUpdate(ctx, rec, program, existingBpfPrograms, expectedBpfPrograms, loadedBpfPrograms, + return r.handleProgCreateOrUpdate(ctx, rec, existingBpfPrograms, expectedBpfPrograms, loadedBpfPrograms, isNodeSelected, isBeingDeleted, mapOwnerStatus) } @@ -877,3 +892,9 @@ func getClientset() (*kubernetes.Clientset, error) { return clientset, nil } + +// sanitize a string to work as a bpfProgram name +func sanitize(name string) string { + name = strings.TrimPrefix(name, "/") + return strings.Replace(strings.Replace(name, "/", "-", -1), "_", "-", -1) +} diff --git a/controllers/bpfman-agent/fentry-program.go b/controllers/bpfman-agent/fentry-program.go index 872f6b77d..69a8d70ee 100644 --- a/controllers/bpfman-agent/fentry-program.go +++ b/controllers/bpfman-agent/fentry-program.go @@ -19,7 +19,6 @@ package bpfmanagent import ( "context" "fmt" - "strings" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" @@ -47,11 +46,19 @@ type FentryProgramReconciler struct { } func (r *FentryProgramReconciler) getFinalizer() string { - return internal.FentryProgramControllerFinalizer + return r.finalizer +} + +func (r *FentryProgramReconciler) getOwner() metav1.Object { + if r.appOwner == nil { + return r.currentFentryProgram + } else { + return r.appOwner + } } func (r *FentryProgramReconciler) getRecType() string { - return internal.FentryString + return r.recType } func (r *FentryProgramReconciler) getProgType() internal.ProgramType { @@ -116,13 +123,12 @@ func (r *FentryProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { func (r *FentryProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfProgramList, error) { progs := &bpfmaniov1alpha1.BpfProgramList{} - // sanitize fentry name to work in a bpfProgram name - sanatizedFentry := strings.Replace(strings.Replace(r.currentFentryProgram.Spec.FunctionName, "/", "-", -1), "_", "-", -1) + sanatizedFentry := sanitize(r.currentFentryProgram.Spec.FunctionName) bpfProgramName := fmt.Sprintf("%s-%s-%s", r.currentFentryProgram.Name, r.NodeName, sanatizedFentry) annotations := map[string]string{internal.FentryProgramFunction: r.currentFentryProgram.Spec.FunctionName} - prog, err := r.createBpfProgram(bpfProgramName, r.getFinalizer(), r.currentFentryProgram, r.getRecType(), annotations) + prog, err := r.createBpfProgram(bpfProgramName, r, annotations) if err != nil { return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err) } @@ -135,6 +141,8 @@ func (r *FentryProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (* func (r *FentryProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { // Initialize node and current program r.currentFentryProgram = &bpfmaniov1alpha1.FentryProgram{} + r.finalizer = internal.FentryProgramControllerFinalizer + r.recType = internal.FentryString r.ourNode = &v1.Node{} r.Logger = ctrl.Log.WithName("fentry") @@ -168,7 +176,8 @@ func (r *FentryProgramReconciler) Reconcile(ctx context.Context, req ctrl.Reques } // Reconcile each FentryProgram. - return r.reconcileCommon(ctx, r, fentryObjects) + _, result, err := r.reconcileCommon(ctx, r, fentryObjects) + return result, err } func (r *FentryProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfProgram, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) { @@ -188,7 +197,7 @@ func (r *FentryProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.Bp }, }, }, - Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.currentFentryProgram.Name}, + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.getOwner().GetName()}, GlobalData: r.currentFentryProgram.Spec.GlobalData, MapOwnerId: mapOwnerId, } diff --git a/controllers/bpfman-agent/fexit-program.go b/controllers/bpfman-agent/fexit-program.go index ca45313cd..6741a71bb 100644 --- a/controllers/bpfman-agent/fexit-program.go +++ b/controllers/bpfman-agent/fexit-program.go @@ -19,7 +19,6 @@ package bpfmanagent import ( "context" "fmt" - "strings" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" @@ -47,11 +46,19 @@ type FexitProgramReconciler struct { } func (r *FexitProgramReconciler) getFinalizer() string { - return internal.FexitProgramControllerFinalizer + return r.finalizer +} + +func (r *FexitProgramReconciler) getOwner() metav1.Object { + if r.appOwner == nil { + return r.currentFexitProgram + } else { + return r.appOwner + } } func (r *FexitProgramReconciler) getRecType() string { - return internal.FexitString + return r.recType } func (r *FexitProgramReconciler) getProgType() internal.ProgramType { @@ -116,13 +123,12 @@ func (r *FexitProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { func (r *FexitProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfProgramList, error) { progs := &bpfmaniov1alpha1.BpfProgramList{} - // sanitize fexit name to work in a bpfProgram name - sanatizedFexit := strings.Replace(strings.Replace(r.currentFexitProgram.Spec.FunctionName, "/", "-", -1), "_", "-", -1) + sanatizedFexit := sanitize(r.currentFexitProgram.Spec.FunctionName) bpfProgramName := fmt.Sprintf("%s-%s-%s", r.currentFexitProgram.Name, r.NodeName, sanatizedFexit) annotations := map[string]string{internal.FexitProgramFunction: r.currentFexitProgram.Spec.FunctionName} - prog, err := r.createBpfProgram(bpfProgramName, r.getFinalizer(), r.currentFexitProgram, r.getRecType(), annotations) + prog, err := r.createBpfProgram(bpfProgramName, r, annotations) if err != nil { return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err) } @@ -135,6 +141,8 @@ func (r *FexitProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*b func (r *FexitProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { // Initialize node and current program r.currentFexitProgram = &bpfmaniov1alpha1.FexitProgram{} + r.finalizer = internal.FexitProgramControllerFinalizer + r.recType = internal.FexitString r.ourNode = &v1.Node{} r.Logger = ctrl.Log.WithName("fexit") @@ -168,7 +176,8 @@ func (r *FexitProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request } // Reconcile each FexitProgram. - return r.reconcileCommon(ctx, r, fexitObjects) + _, result, err := r.reconcileCommon(ctx, r, fexitObjects) + return result, err } func (r *FexitProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfProgram, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) { @@ -188,7 +197,7 @@ func (r *FexitProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.Bpf }, }, }, - Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.currentFexitProgram.Name}, + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.getOwner().GetName()}, GlobalData: r.currentFexitProgram.Spec.GlobalData, MapOwnerId: mapOwnerId, } diff --git a/controllers/bpfman-agent/kprobe-program.go b/controllers/bpfman-agent/kprobe-program.go index 01766e103..eaeb684fb 100644 --- a/controllers/bpfman-agent/kprobe-program.go +++ b/controllers/bpfman-agent/kprobe-program.go @@ -19,7 +19,6 @@ package bpfmanagent import ( "context" "fmt" - "strings" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" @@ -47,11 +46,19 @@ type KprobeProgramReconciler struct { } func (r *KprobeProgramReconciler) getFinalizer() string { - return internal.KprobeProgramControllerFinalizer + return r.finalizer +} + +func (r *KprobeProgramReconciler) getOwner() metav1.Object { + if r.appOwner == nil { + return r.currentKprobeProgram + } else { + return r.appOwner + } } func (r *KprobeProgramReconciler) getRecType() string { - return internal.Kprobe.String() + return r.recType } func (r *KprobeProgramReconciler) getProgType() internal.ProgramType { @@ -116,13 +123,12 @@ func (r *KprobeProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { func (r *KprobeProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfProgramList, error) { progs := &bpfmaniov1alpha1.BpfProgramList{} - // sanitize kprobe name to work in a bpfProgram name - sanatizedKprobe := strings.Replace(strings.Replace(r.currentKprobeProgram.Spec.FunctionName, "/", "-", -1), "_", "-", -1) + sanatizedKprobe := sanitize(r.currentKprobeProgram.Spec.FunctionName) bpfProgramName := fmt.Sprintf("%s-%s-%s", r.currentKprobeProgram.Name, r.NodeName, sanatizedKprobe) annotations := map[string]string{internal.KprobeProgramFunction: r.currentKprobeProgram.Spec.FunctionName} - prog, err := r.createBpfProgram(bpfProgramName, r.getFinalizer(), r.currentKprobeProgram, r.getRecType(), annotations) + prog, err := r.createBpfProgram(bpfProgramName, r, annotations) if err != nil { return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err) } @@ -135,6 +141,8 @@ func (r *KprobeProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (* func (r *KprobeProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { // Initialize node and current program r.currentKprobeProgram = &bpfmaniov1alpha1.KprobeProgram{} + r.finalizer = internal.KprobeProgramControllerFinalizer + r.recType = internal.Kprobe.String() r.ourNode = &v1.Node{} r.Logger = ctrl.Log.WithName("kprobe") @@ -168,7 +176,8 @@ func (r *KprobeProgramReconciler) Reconcile(ctx context.Context, req ctrl.Reques } // Reconcile each KprobeProgram. - return r.reconcileCommon(ctx, r, kprobeObjects) + _, result, err := r.reconcileCommon(ctx, r, kprobeObjects) + return result, err } func (r *KprobeProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfProgram, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) { @@ -194,7 +203,7 @@ func (r *KprobeProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.Bp }, }, }, - Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.currentKprobeProgram.Name}, + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.getOwner().GetName()}, GlobalData: r.currentKprobeProgram.Spec.GlobalData, MapOwnerId: mapOwnerId, } diff --git a/controllers/bpfman-agent/tc-program.go b/controllers/bpfman-agent/tc-program.go index 935b04f26..f1499937e 100644 --- a/controllers/bpfman-agent/tc-program.go +++ b/controllers/bpfman-agent/tc-program.go @@ -48,11 +48,19 @@ type TcProgramReconciler struct { } func (r *TcProgramReconciler) getFinalizer() string { - return internal.TcProgramControllerFinalizer + return r.finalizer +} + +func (r *TcProgramReconciler) getOwner() metav1.Object { + if r.appOwner == nil { + return r.currentTcProgram + } else { + return r.appOwner + } } func (r *TcProgramReconciler) getRecType() string { - return internal.Tc.String() + return r.recType } func (r *TcProgramReconciler) getProgType() internal.ProgramType { @@ -164,7 +172,7 @@ func (r *TcProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpfm bpfProgramName := fmt.Sprintf("%s-%s-%s", r.currentTcProgram.Name, r.NodeName, iface) annotations := map[string]string{internal.TcProgramInterface: iface} - prog, err := r.createBpfProgram(bpfProgramName, r.getFinalizer(), r.currentTcProgram, r.getRecType(), annotations) + prog, err := r.createBpfProgram(bpfProgramName, r, annotations) if err != nil { return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err) } @@ -178,6 +186,8 @@ func (r *TcProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpfm func (r *TcProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { // Initialize node and current program r.currentTcProgram = &bpfmaniov1alpha1.TcProgram{} + r.finalizer = internal.TcProgramControllerFinalizer + r.recType = internal.Tc.String() r.ourNode = &v1.Node{} r.Logger = ctrl.Log.WithName("tc") @@ -211,7 +221,8 @@ func (r *TcProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } // Reconcile each TcProgram. - return r.reconcileCommon(ctx, r, tcObjects) + _, result, err := r.reconcileCommon(ctx, r, tcObjects) + return result, err } func (r *TcProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfProgram, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) { @@ -234,7 +245,7 @@ func (r *TcProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfPro }, }, }, - Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.currentTcProgram.Name}, + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.getOwner().GetName()}, GlobalData: r.currentTcProgram.Spec.GlobalData, MapOwnerId: mapOwnerId, } diff --git a/controllers/bpfman-agent/tracepoint-program.go b/controllers/bpfman-agent/tracepoint-program.go index fdc00bf53..b7350fe2d 100644 --- a/controllers/bpfman-agent/tracepoint-program.go +++ b/controllers/bpfman-agent/tracepoint-program.go @@ -19,7 +19,6 @@ package bpfmanagent import ( "context" "fmt" - "strings" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" @@ -47,11 +46,19 @@ type TracepointProgramReconciler struct { } func (r *TracepointProgramReconciler) getFinalizer() string { - return internal.TracepointProgramControllerFinalizer + return r.finalizer +} + +func (r *TracepointProgramReconciler) getOwner() metav1.Object { + if r.appOwner == nil { + return r.currentTracepointProgram + } else { + return r.appOwner + } } func (r *TracepointProgramReconciler) getRecType() string { - return internal.Tracepoint.String() + return r.recType } func (r *TracepointProgramReconciler) getProgType() internal.ProgramType { @@ -117,13 +124,12 @@ func (r *TracepointProgramReconciler) getExpectedBpfPrograms(ctx context.Context progs := &bpfmaniov1alpha1.BpfProgramList{} for _, tracepoint := range r.currentTracepointProgram.Spec.Names { - // sanitize tracepoint name to work in a bpfProgram name - sanatizedTrace := strings.Replace(strings.Replace(tracepoint, "/", "-", -1), "_", "-", -1) + sanatizedTrace := sanitize(tracepoint) bpfProgramName := fmt.Sprintf("%s-%s-%s", r.currentTracepointProgram.Name, r.NodeName, sanatizedTrace) annotations := map[string]string{internal.TracepointProgramTracepoint: tracepoint} - prog, err := r.createBpfProgram(bpfProgramName, r.getFinalizer(), r.currentTracepointProgram, r.getRecType(), annotations) + prog, err := r.createBpfProgram(bpfProgramName, r, annotations) if err != nil { return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err) } @@ -137,6 +143,8 @@ func (r *TracepointProgramReconciler) getExpectedBpfPrograms(ctx context.Context func (r *TracepointProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { // Initialize node and current program r.currentTracepointProgram = &bpfmaniov1alpha1.TracepointProgram{} + r.finalizer = internal.TracepointProgramControllerFinalizer + r.recType = internal.Tracepoint.String() r.ourNode = &v1.Node{} r.Logger = ctrl.Log.WithName("tracept") @@ -170,7 +178,8 @@ func (r *TracepointProgramReconciler) Reconcile(ctx context.Context, req ctrl.Re } // Reconcile each TcProgram. - return r.reconcileCommon(ctx, r, tracepointObjects) + _, result, err := r.reconcileCommon(ctx, r, tracepointObjects) + return result, err } func (r *TracepointProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfProgram, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) { @@ -190,7 +199,7 @@ func (r *TracepointProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha }, }, }, - Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.currentTracepointProgram.Name}, + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.getOwner().GetName()}, GlobalData: r.currentTracepointProgram.Spec.GlobalData, MapOwnerId: mapOwnerId, } diff --git a/controllers/bpfman-agent/uprobe-program.go b/controllers/bpfman-agent/uprobe-program.go index c1092ae01..8db4b2b00 100644 --- a/controllers/bpfman-agent/uprobe-program.go +++ b/controllers/bpfman-agent/uprobe-program.go @@ -20,7 +20,6 @@ import ( "context" "fmt" "strconv" - "strings" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" @@ -48,11 +47,19 @@ type UprobeProgramReconciler struct { } func (r *UprobeProgramReconciler) getFinalizer() string { - return internal.UprobeProgramControllerFinalizer + return r.finalizer +} + +func (r *UprobeProgramReconciler) getOwner() metav1.Object { + if r.appOwner == nil { + return r.currentUprobeProgram + } else { + return r.appOwner + } } func (r *UprobeProgramReconciler) getRecType() string { - return internal.UprobeString + return r.recType } func (r *UprobeProgramReconciler) getProgType() internal.ProgramType { @@ -149,8 +156,7 @@ func (r *UprobeProgramReconciler) getUprobeContainerInfo(ctx context.Context) (* func (r *UprobeProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfProgramList, error) { progs := &bpfmaniov1alpha1.BpfProgramList{} - // sanitize uprobe name to work in a bpfProgram name - sanatizedUprobe := strings.Replace(strings.Replace(r.currentUprobeProgram.Spec.Target, "/", "-", -1), "_", "-", -1) + sanatizedUprobe := sanitize(r.currentUprobeProgram.Spec.Target) bpfProgramNameBase := fmt.Sprintf("%s-%s-%s", r.currentUprobeProgram.Name, r.NodeName, sanatizedUprobe) if r.currentUprobeProgram.Spec.Containers != nil { @@ -171,7 +177,7 @@ func (r *UprobeProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (* bpfProgramName := fmt.Sprintf("%s-%s", bpfProgramNameBase, "no-containers-on-node") - prog, err := r.createBpfProgram(bpfProgramName, r.getFinalizer(), r.currentUprobeProgram, r.getRecType(), annotations) + prog, err := r.createBpfProgram(bpfProgramName, r, annotations) if err != nil { return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramNameBase, err) } @@ -188,7 +194,7 @@ func (r *UprobeProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (* bpfProgramName := fmt.Sprintf("%s-%s-%s", bpfProgramNameBase, container.podName, container.containerName) - prog, err := r.createBpfProgram(bpfProgramName, r.getFinalizer(), r.currentUprobeProgram, r.getRecType(), annotations) + prog, err := r.createBpfProgram(bpfProgramName, r, annotations) if err != nil { return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err) } @@ -199,7 +205,7 @@ func (r *UprobeProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (* } else { annotations := map[string]string{internal.UprobeProgramTarget: r.currentUprobeProgram.Spec.Target} - prog, err := r.createBpfProgram(bpfProgramNameBase, r.getFinalizer(), r.currentUprobeProgram, r.getRecType(), annotations) + prog, err := r.createBpfProgram(bpfProgramNameBase, r, annotations) if err != nil { return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramNameBase, err) } @@ -213,6 +219,8 @@ func (r *UprobeProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (* func (r *UprobeProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { // Initialize node and current program r.currentUprobeProgram = &bpfmaniov1alpha1.UprobeProgram{} + r.finalizer = internal.UprobeProgramControllerFinalizer + r.recType = internal.UprobeString r.ourNode = &v1.Node{} r.Logger = ctrl.Log.WithName("uprobe") @@ -246,7 +254,8 @@ func (r *UprobeProgramReconciler) Reconcile(ctx context.Context, req ctrl.Reques } // Reconcile each TcProgram. - return r.reconcileCommon(ctx, r, uprobeObjects) + _, result, err := r.reconcileCommon(ctx, r, uprobeObjects) + return result, err } func (r *UprobeProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfProgram, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) { @@ -292,7 +301,7 @@ func (r *UprobeProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.Bp UprobeAttachInfo: uprobeAttachInfo, }, }, - Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.currentUprobeProgram.Name}, + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.getOwner().GetName()}, GlobalData: r.currentUprobeProgram.Spec.GlobalData, MapOwnerId: mapOwnerId, } diff --git a/controllers/bpfman-agent/xdp-program.go b/controllers/bpfman-agent/xdp-program.go index ff185f668..88afccfe7 100644 --- a/controllers/bpfman-agent/xdp-program.go +++ b/controllers/bpfman-agent/xdp-program.go @@ -47,11 +47,19 @@ type XdpProgramReconciler struct { } func (r *XdpProgramReconciler) getFinalizer() string { - return internal.XdpProgramControllerFinalizer + return r.finalizer +} + +func (r *XdpProgramReconciler) getOwner() metav1.Object { + if r.appOwner == nil { + return r.currentXdpProgram + } else { + return r.appOwner + } } func (r *XdpProgramReconciler) getRecType() string { - return internal.Xdp.String() + return r.recType } func (r *XdpProgramReconciler) getProgType() internal.ProgramType { @@ -150,7 +158,7 @@ func (r *XdpProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpf bpfProgramName := fmt.Sprintf("%s-%s-%s", r.currentXdpProgram.Name, r.NodeName, iface) annotations := map[string]string{internal.XdpProgramInterface: iface} - prog, err := r.createBpfProgram(bpfProgramName, r.getFinalizer(), r.currentXdpProgram, r.getRecType(), annotations) + prog, err := r.createBpfProgram(bpfProgramName, r, annotations) if err != nil { return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err) } @@ -164,6 +172,8 @@ func (r *XdpProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpf func (r *XdpProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { // Initialize node and current program r.currentXdpProgram = &bpfmaniov1alpha1.XdpProgram{} + r.finalizer = internal.XdpProgramControllerFinalizer + r.recType = internal.Xdp.String() r.ourNode = &v1.Node{} r.Logger = ctrl.Log.WithName("xdp") @@ -196,7 +206,8 @@ func (r *XdpProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) } // Reconcile each TcProgram. - return r.reconcileCommon(ctx, r, xdpObjects) + _, result, err := r.reconcileCommon(ctx, r, xdpObjects) + return result, err } func (r *XdpProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfProgram, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) { @@ -218,7 +229,7 @@ func (r *XdpProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfPr }, }, }, - Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.currentXdpProgram.Name}, + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.getOwner().GetName()}, GlobalData: r.currentXdpProgram.Spec.GlobalData, MapOwnerId: mapOwnerId, } diff --git a/controllers/bpfman-operator/application-program_test.go b/controllers/bpfman-operator/application-program_test.go new file mode 100644 index 000000000..257b977a4 --- /dev/null +++ b/controllers/bpfman-operator/application-program_test.go @@ -0,0 +1,215 @@ +/* +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 appProgramReconcile(t *testing.T, multiCondition bool) { + var ( + name = "fakeAppProgram" + bytecodePath = "/tmp/hello.o" + bpfFentryFunctionName = "fentry_test" + bpfKprobeFunctionName = "kprobe_test" + bpfTracepointFunctionName = "tracepoint-test" + fakeNode = testutils.NewNode("fake-control-plane") + functionFentryName = "do_unlinkat" + functionKprobeName = "try_to_wake_up" + tracepointName = "syscalls/sys_enter_setitimer" + offset = 0 + retprobe = false + ctx = context.TODO() + bpfProgName = fmt.Sprintf("%s-%s", name, fakeNode.Name) + ) + // A AppProgram object with metadata and spec. + App := &bpfmaniov1alpha1.BpfApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: bpfmaniov1alpha1.BpfApplicationSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + Programs: []bpfmaniov1alpha1.BpfApplicationProgram{ + { + Type: bpfmaniov1alpha1.ProgTypeFentry, + Fentry: &bpfmaniov1alpha1.FentryProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFentryFunctionName, + }, + FunctionName: functionFentryName, + }, + }, + { + Type: bpfmaniov1alpha1.ProgTypeKprobe, + Kprobe: &bpfmaniov1alpha1.KprobeProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfKprobeFunctionName, + }, + FunctionName: functionKprobeName, + Offset: uint64(offset), + RetProbe: retprobe, + }, + }, + { + Type: bpfmaniov1alpha1.ProgTypeTracepoint, + Tracepoint: &bpfmaniov1alpha1.TracepointProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfTracepointFunctionName, + }, + Names: []string{tracepointName}, + }, + }, + }, + }, + } + + // The expected accompanying BpfProgram object + expectedBpfProg := &bpfmaniov1alpha1.BpfProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: bpfProgName, + OwnerReferences: []metav1.OwnerReference{ + { + Name: App.Name, + Controller: &[]bool{true}[0], + }, + }, + Labels: map[string]string{internal.BpfProgramOwnerLabel: App.Name, internal.K8sHostLabel: fakeNode.Name}, + Finalizers: []string{internal.BpfApplicationControllerFinalizer}, + }, + 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.BpfProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(App).WithRuntimeObjects(objs...).Build() + + rc := ReconcilerCommon{ + Client: cl, + Scheme: s, + } + + // 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} + + // 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 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 BpfProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: App.Name, Namespace: metav1.NamespaceAll}, 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 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 Program object status") + } + // Make sure we have 2 conditions + require.Equal(t, 2, len(App.Status.Conditions)) + } + + // Second reconcile should check bpfProgram 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 BpfProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: App.Name, Namespace: metav1.NamespaceAll}, 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 TestAppProgramReconcile(t *testing.T) { + appProgramReconcile(t, false) +} + +func TestAppUpdateStatus(t *testing.T) { + appProgramReconcile(t, true) +} diff --git a/controllers/bpfman-operator/application-programs.go b/controllers/bpfman-operator/application-programs.go new file mode 100644 index 000000000..0001c3430 --- /dev/null +++ b/controllers/bpfman-operator/application-programs.go @@ -0,0 +1,119 @@ +/* +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" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + 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/log" + "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=bpfapplications,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfapplications/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfapplications/finalizers,verbs=update + +// BpfApplicationReconciler reconciles a BpfApplication object +type BpfApplicationReconciler struct { + ReconcilerCommon +} + +func (r *BpfApplicationReconciler) getRecCommon() *ReconcilerCommon { + return &r.ReconcilerCommon +} + +func (r *BpfApplicationReconciler) getFinalizer() string { + return internal.BpfApplicationControllerFinalizer +} + +// SetupWithManager sets up the controller with the Manager. +func (r *BpfApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.BpfApplication{}). + // Watch bpfPrograms which are owned by BpfApplications + Watches( + &bpfmaniov1alpha1.BpfProgram{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.ApplicationString))), + ). + Complete(r) +} + +func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Logger = log.FromContext(ctx) + + appProgram := &bpfmaniov1alpha1.BpfApplication{} + if err := r.Get(ctx, req.NamespacedName, appProgram); err != nil { + // Reconcile was triggered by bpfProgram event, get parent appProgram Object. + if errors.IsNotFound(err) { + bpfProgram := &bpfmaniov1alpha1.BpfProgram{} + 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", "Name", req.NamespacedName) + } else { + r.Logger.Error(err, "failed getting bpfProgram Object", "Name", req.NamespacedName) + } + 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 bpfProgram Object owner") + } + + if err := r.Get(ctx, types.NamespacedName{Namespace: corev1.NamespaceAll, Name: ownerRef.Name}, appProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.Info("Application Programs from ownerRef not found stale reconcile exiting", "Name", req.NamespacedName) + } else { + r.Logger.Error(err, "failed getting Application Programs Object from ownerRef", "Name", req.NamespacedName) + } + return ctrl.Result{}, nil + } + + } else { + r.Logger.Error(err, "failed getting Application Programs Object", "Name", req.NamespacedName) + return ctrl.Result{}, nil + } + } + + return reconcileBpfProgram(ctx, r, appProgram) +} + +func (r *BpfApplicationReconciler) updateStatus(ctx context.Context, 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{} + if err := r.Get(ctx, types.NamespacedName{Namespace: corev1.NamespaceAll, 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/common.go b/controllers/bpfman-operator/common.go index fccec2792..2bcef68f3 100644 --- a/controllers/bpfman-operator/common.go +++ b/controllers/bpfman-operator/common.go @@ -161,7 +161,7 @@ func (r *ReconcilerCommon) removeFinalizer(ctx context.Context, prog client.Obje } func (r *ReconcilerCommon) addFinalizer(ctx context.Context, prog client.Object, finalizer string) (ctrl.Result, error) { - controllerutil.AddFinalizer(prog, internal.BpfmanOperatorFinalizer) + controllerutil.AddFinalizer(prog, finalizer) err := r.Update(ctx, prog) if err != nil { diff --git a/internal/constants.go b/internal/constants.go index ae9b40191..8ddc43a06 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -28,7 +28,6 @@ const ( UprobeNoContainersOnNode = "bpfman.io.uprobeprogramcontroller/nocontainersonnode" FentryProgramFunction = "bpfman.io.fentryprogramcontroller/function" FexitProgramFunction = "bpfman.io.fexitprogramcontroller/function" - BpfProgramOwnerLabel = "bpfman.io/ownedByProgram" K8sHostLabel = "kubernetes.io/hostname" DiscoveredLabel = "bpfman.io/discoveredProgram" IdAnnotation = "bpfman.io/ProgramId" @@ -50,6 +49,16 @@ const ( DefaultPath = "/run/bpfman-sock/bpfman.sock" DefaultPort = 50051 DefaultEnabled = true + // BpfProgramOwnerLabel 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 + // BpfApplication object. + BpfProgramOwnerLabel = "bpfman.io/ownedByProgram" + // BpfParentProgram is the name of the current program that caused the + // creation of 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 generated for the given BpfApplication program. + BpfParentProgram = "bpfman.io/parentProgram" ) // ----------------------------------------------------------------------------- @@ -81,6 +90,8 @@ const ( // FexitProgramControllerFinalizer is the finalizer that holds a Fexit // BpfProgram object from deletion until cleanup can be performed. FexitProgramControllerFinalizer = "bpfman.io.fexitprogramcontroller/finalizer" + // BpfApplicationFinalizer is the finalizer that holds a BpfApplication + BpfApplicationControllerFinalizer = "bpfman.io.bpfapplicationcontroller/finalizer" ) // Must match the kernel's `bpf_prog_type` enum. @@ -228,6 +239,7 @@ func (p ProgramType) String() string { const UprobeString = "uprobe" const FentryString = "fentry" const FexitString = "fexit" +const ApplicationString = "application" type ReconcileResult uint8 diff --git a/pkg/client/apis/v1alpha1/bpfapplication.go b/pkg/client/apis/v1alpha1/bpfapplication.go new file mode 100644 index 000000000..3c154a5ef --- /dev/null +++ b/pkg/client/apis/v1alpha1/bpfapplication.go @@ -0,0 +1,68 @@ +/* +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" +) + +// BpfApplicationLister helps list BpfApplications. +// All objects returned here must be treated as read-only. +type BpfApplicationLister interface { + // List lists all BpfApplications in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.BpfApplication, err error) + // Get retrieves the BpfApplication from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.BpfApplication, error) + BpfApplicationListerExpansion +} + +// bpfApplicationLister implements the BpfApplicationLister interface. +type bpfApplicationLister struct { + indexer cache.Indexer +} + +// NewBpfApplicationLister returns a new BpfApplicationLister. +func NewBpfApplicationLister(indexer cache.Indexer) BpfApplicationLister { + return &bpfApplicationLister{indexer: indexer} +} + +// List lists all BpfApplications in the indexer. +func (s *bpfApplicationLister) List(selector labels.Selector) (ret []*v1alpha1.BpfApplication, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.BpfApplication)) + }) + return ret, err +} + +// Get retrieves the BpfApplication from the index for a given name. +func (s *bpfApplicationLister) Get(name string) (*v1alpha1.BpfApplication, error) { + obj, exists, err := s.indexer.GetByKey(name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("bpfapplication"), name) + } + return obj.(*v1alpha1.BpfApplication), nil +} diff --git a/pkg/client/apis/v1alpha1/expansion_generated.go b/pkg/client/apis/v1alpha1/expansion_generated.go index 5ee0564f6..7c31cea1b 100644 --- a/pkg/client/apis/v1alpha1/expansion_generated.go +++ b/pkg/client/apis/v1alpha1/expansion_generated.go @@ -18,6 +18,10 @@ limitations under the License. package v1alpha1 +// BpfApplicationListerExpansion allows custom methods to be added to +// BpfApplicationLister. +type BpfApplicationListerExpansion interface{} + // BpfProgramListerExpansion allows custom methods to be added to // BpfProgramLister. type BpfProgramListerExpansion interface{} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/apis_client.go b/pkg/client/clientset/typed/apis/v1alpha1/apis_client.go index 296b4cf3a..c1d54ecdd 100644 --- a/pkg/client/clientset/typed/apis/v1alpha1/apis_client.go +++ b/pkg/client/clientset/typed/apis/v1alpha1/apis_client.go @@ -28,6 +28,7 @@ import ( type BpfmanV1alpha1Interface interface { RESTClient() rest.Interface + BpfApplicationsGetter BpfProgramsGetter FentryProgramsGetter FexitProgramsGetter @@ -43,6 +44,10 @@ type BpfmanV1alpha1Client struct { restClient rest.Interface } +func (c *BpfmanV1alpha1Client) BpfApplications() BpfApplicationInterface { + return newBpfApplications(c) +} + func (c *BpfmanV1alpha1Client) BpfPrograms() BpfProgramInterface { return newBpfPrograms(c) } diff --git a/pkg/client/clientset/typed/apis/v1alpha1/bpfapplication.go b/pkg/client/clientset/typed/apis/v1alpha1/bpfapplication.go new file mode 100644 index 000000000..3c57ae534 --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/bpfapplication.go @@ -0,0 +1,184 @@ +/* +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" +) + +// BpfApplicationsGetter has a method to return a BpfApplicationInterface. +// A group's client should implement this interface. +type BpfApplicationsGetter interface { + BpfApplications() BpfApplicationInterface +} + +// BpfApplicationInterface has methods to work with BpfApplication resources. +type BpfApplicationInterface interface { + Create(ctx context.Context, bpfApplication *v1alpha1.BpfApplication, opts v1.CreateOptions) (*v1alpha1.BpfApplication, error) + Update(ctx context.Context, bpfApplication *v1alpha1.BpfApplication, opts v1.UpdateOptions) (*v1alpha1.BpfApplication, error) + UpdateStatus(ctx context.Context, bpfApplication *v1alpha1.BpfApplication, opts v1.UpdateOptions) (*v1alpha1.BpfApplication, 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.BpfApplication, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.BpfApplicationList, 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.BpfApplication, err error) + BpfApplicationExpansion +} + +// bpfApplications implements BpfApplicationInterface +type bpfApplications struct { + client rest.Interface +} + +// newBpfApplications returns a BpfApplications +func newBpfApplications(c *BpfmanV1alpha1Client) *bpfApplications { + return &bpfApplications{ + client: c.RESTClient(), + } +} + +// Get takes name of the bpfApplication, and returns the corresponding bpfApplication object, and an error if there is any. +func (c *bpfApplications) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.BpfApplication, err error) { + result = &v1alpha1.BpfApplication{} + err = c.client.Get(). + Resource("bpfapplications"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of BpfApplications that match those selectors. +func (c *bpfApplications) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.BpfApplicationList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.BpfApplicationList{} + err = c.client.Get(). + Resource("bpfapplications"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested bpfApplications. +func (c *bpfApplications) 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(). + Resource("bpfapplications"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a bpfApplication and creates it. Returns the server's representation of the bpfApplication, and an error, if there is any. +func (c *bpfApplications) Create(ctx context.Context, bpfApplication *v1alpha1.BpfApplication, opts v1.CreateOptions) (result *v1alpha1.BpfApplication, err error) { + result = &v1alpha1.BpfApplication{} + err = c.client.Post(). + Resource("bpfapplications"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(bpfApplication). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a bpfApplication and updates it. Returns the server's representation of the bpfApplication, and an error, if there is any. +func (c *bpfApplications) Update(ctx context.Context, bpfApplication *v1alpha1.BpfApplication, opts v1.UpdateOptions) (result *v1alpha1.BpfApplication, err error) { + result = &v1alpha1.BpfApplication{} + err = c.client.Put(). + Resource("bpfapplications"). + Name(bpfApplication.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(bpfApplication). + 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 *bpfApplications) UpdateStatus(ctx context.Context, bpfApplication *v1alpha1.BpfApplication, opts v1.UpdateOptions) (result *v1alpha1.BpfApplication, err error) { + result = &v1alpha1.BpfApplication{} + err = c.client.Put(). + Resource("bpfapplications"). + Name(bpfApplication.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(bpfApplication). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the bpfApplication and deletes it. Returns an error if one occurs. +func (c *bpfApplications) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Resource("bpfapplications"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *bpfApplications) 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(). + Resource("bpfapplications"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched bpfApplication. +func (c *bpfApplications) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.BpfApplication, err error) { + result = &v1alpha1.BpfApplication{} + err = c.client.Patch(pt). + Resource("bpfapplications"). + 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 8d7ce4242..7acfcffc8 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 @@ -28,6 +28,10 @@ type FakeBpfmanV1alpha1 struct { *testing.Fake } +func (c *FakeBpfmanV1alpha1) BpfApplications() v1alpha1.BpfApplicationInterface { + return &FakeBpfApplications{c} +} + func (c *FakeBpfmanV1alpha1) BpfPrograms() v1alpha1.BpfProgramInterface { return &FakeBpfPrograms{c} } diff --git a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_bpfapplication.go b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_bpfapplication.go new file mode 100644 index 000000000..0b03c0179 --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_bpfapplication.go @@ -0,0 +1,132 @@ +/* +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" +) + +// FakeBpfApplications implements BpfApplicationInterface +type FakeBpfApplications struct { + Fake *FakeBpfmanV1alpha1 +} + +var bpfapplicationsResource = v1alpha1.SchemeGroupVersion.WithResource("bpfapplications") + +var bpfapplicationsKind = v1alpha1.SchemeGroupVersion.WithKind("BpfApplication") + +// Get takes name of the bpfApplication, and returns the corresponding bpfApplication object, and an error if there is any. +func (c *FakeBpfApplications) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.BpfApplication, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootGetAction(bpfapplicationsResource, name), &v1alpha1.BpfApplication{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfApplication), err +} + +// List takes label and field selectors, and returns the list of BpfApplications that match those selectors. +func (c *FakeBpfApplications) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.BpfApplicationList, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootListAction(bpfapplicationsResource, bpfapplicationsKind, opts), &v1alpha1.BpfApplicationList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.BpfApplicationList{ListMeta: obj.(*v1alpha1.BpfApplicationList).ListMeta} + for _, item := range obj.(*v1alpha1.BpfApplicationList).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 bpfApplications. +func (c *FakeBpfApplications) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchAction(bpfapplicationsResource, opts)) +} + +// Create takes the representation of a bpfApplication and creates it. Returns the server's representation of the bpfApplication, and an error, if there is any. +func (c *FakeBpfApplications) Create(ctx context.Context, bpfApplication *v1alpha1.BpfApplication, opts v1.CreateOptions) (result *v1alpha1.BpfApplication, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootCreateAction(bpfapplicationsResource, bpfApplication), &v1alpha1.BpfApplication{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfApplication), err +} + +// Update takes the representation of a bpfApplication and updates it. Returns the server's representation of the bpfApplication, and an error, if there is any. +func (c *FakeBpfApplications) Update(ctx context.Context, bpfApplication *v1alpha1.BpfApplication, opts v1.UpdateOptions) (result *v1alpha1.BpfApplication, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateAction(bpfapplicationsResource, bpfApplication), &v1alpha1.BpfApplication{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfApplication), 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 *FakeBpfApplications) UpdateStatus(ctx context.Context, bpfApplication *v1alpha1.BpfApplication, opts v1.UpdateOptions) (*v1alpha1.BpfApplication, error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(bpfapplicationsResource, "status", bpfApplication), &v1alpha1.BpfApplication{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfApplication), err +} + +// Delete takes name of the bpfApplication and deletes it. Returns an error if one occurs. +func (c *FakeBpfApplications) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteActionWithOptions(bpfapplicationsResource, name, opts), &v1alpha1.BpfApplication{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeBpfApplications) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewRootDeleteCollectionAction(bpfapplicationsResource, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.BpfApplicationList{}) + return err +} + +// Patch applies the patch and returns the patched bpfApplication. +func (c *FakeBpfApplications) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.BpfApplication, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceAction(bpfapplicationsResource, name, pt, data, subresources...), &v1alpha1.BpfApplication{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfApplication), err +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/generated_expansion.go b/pkg/client/clientset/typed/apis/v1alpha1/generated_expansion.go index ff5a8ed4d..6c297eeee 100644 --- a/pkg/client/clientset/typed/apis/v1alpha1/generated_expansion.go +++ b/pkg/client/clientset/typed/apis/v1alpha1/generated_expansion.go @@ -18,6 +18,8 @@ limitations under the License. package v1alpha1 +type BpfApplicationExpansion interface{} + type BpfProgramExpansion interface{} type FentryProgramExpansion interface{} diff --git a/pkg/client/externalversions/apis/v1alpha1/bpfapplication.go b/pkg/client/externalversions/apis/v1alpha1/bpfapplication.go new file mode 100644 index 000000000..e2cff2a7e --- /dev/null +++ b/pkg/client/externalversions/apis/v1alpha1/bpfapplication.go @@ -0,0 +1,89 @@ +/* +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" +) + +// BpfApplicationInformer provides access to a shared informer and lister for +// BpfApplications. +type BpfApplicationInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.BpfApplicationLister +} + +type bpfApplicationInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewBpfApplicationInformer constructs a new informer for BpfApplication 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 NewBpfApplicationInformer(client clientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredBpfApplicationInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredBpfApplicationInformer constructs a new informer for BpfApplication 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 NewFilteredBpfApplicationInformer(client clientset.Interface, 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().BpfApplications().List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().BpfApplications().Watch(context.TODO(), options) + }, + }, + &apisv1alpha1.BpfApplication{}, + resyncPeriod, + indexers, + ) +} + +func (f *bpfApplicationInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredBpfApplicationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *bpfApplicationInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisv1alpha1.BpfApplication{}, f.defaultInformer) +} + +func (f *bpfApplicationInformer) Lister() v1alpha1.BpfApplicationLister { + return v1alpha1.NewBpfApplicationLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/externalversions/apis/v1alpha1/interface.go b/pkg/client/externalversions/apis/v1alpha1/interface.go index 362646dba..0b792e2fe 100644 --- a/pkg/client/externalversions/apis/v1alpha1/interface.go +++ b/pkg/client/externalversions/apis/v1alpha1/interface.go @@ -24,6 +24,8 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { + // BpfApplications returns a BpfApplicationInformer. + BpfApplications() BpfApplicationInformer // BpfPrograms returns a BpfProgramInformer. BpfPrograms() BpfProgramInformer // FentryPrograms returns a FentryProgramInformer. @@ -53,6 +55,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } +// BpfApplications returns a BpfApplicationInformer. +func (v *version) BpfApplications() BpfApplicationInformer { + return &bpfApplicationInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} + // BpfPrograms returns a BpfProgramInformer. func (v *version) BpfPrograms() BpfProgramInformer { return &bpfProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} diff --git a/pkg/client/externalversions/generic.go b/pkg/client/externalversions/generic.go index 85295806c..e045549cf 100644 --- a/pkg/client/externalversions/generic.go +++ b/pkg/client/externalversions/generic.go @@ -53,6 +53,8 @@ func (f *genericInformer) Lister() cache.GenericLister { func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { // 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("bpfprograms"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().BpfPrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("fentryprograms"): diff --git a/pkg/helpers/helpers.go b/pkg/helpers/helpers.go index f48a152d6..62bfe22cf 100644 --- a/pkg/helpers/helpers.go +++ b/pkg/helpers/helpers.go @@ -21,21 +21,15 @@ import ( "fmt" "time" - //bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" bpfmanclientset "github.com/bpfman/bpfman-operator/pkg/client/clientset" - //"k8s.io/apimachinery/pkg/api/errors" - //"k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - //"k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/discovery" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" - - bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + ctrl "sigs.k8s.io/controller-runtime" ) // Must match the internal bpfman-api mappings