From 7bfea5c462447f7aa773f5d4fc5f4a60c309a009 Mon Sep 17 00:00:00 2001 From: Billy McFall <22157057+Billy99@users.noreply.github.com> Date: Tue, 3 Dec 2024 16:22:51 -0500 Subject: [PATCH] Add core code to support namespace based CRDs For security reasons, cluster admins may want to limit certain applications to only loading eBPF programs within a given namespace. Currently, all bpfman Custom Resource Definitions (CRDs) are Cluster scoped. To provide cluster admins with tighter controls on eBPF program loading, some of the bpfman CRDs also need to be Namespace scoped. See Design Doc: https://github.com/bpfman/bpfman/pull/1359 Signed-off-by: Billy McFall <22157057+Billy99@users.noreply.github.com> --- PROJECT | 191 +-- README.md | 52 +- ...ation_types.go => bpfApplication_types.go} | 0 apis/v1alpha1/bpfNsApplication_types.go | 97 ++ apis/v1alpha1/bpfNsProgram_types.go | 79 ++ ...pfprogram_types.go => bpfProgram_types.go} | 30 + apis/v1alpha1/kprobeProgram_types.go | 4 - apis/v1alpha1/shared_types.go | 14 + apis/v1alpha1/tcNsProgram_types.go | 90 ++ apis/v1alpha1/tcProgram_types.go | 2 +- apis/v1alpha1/tcxNsProgram_types.go | 83 ++ apis/v1alpha1/tcxProgram_types.go | 4 +- apis/v1alpha1/uprobeNsProgram_types.go | 95 ++ apis/v1alpha1/uprobeProgram_types.go | 2 +- apis/v1alpha1/xdpNsProgram_types.go | 83 ++ apis/v1alpha1/xdpProgram_types.go | 2 +- apis/v1alpha1/zz_generated.deepcopy.go | 591 +++++++++ apis/v1alpha1/zz_generated.register.go | 12 + ...c.authorization.k8s.io_v1_clusterrole.yaml | 66 + ...ole_rbac.authorization.k8s.io_v1_role.yaml | 52 + ...ole_rbac.authorization.k8s.io_v1_role.yaml | 31 + ...ole_rbac.authorization.k8s.io_v1_role.yaml | 27 + ...ole_rbac.authorization.k8s.io_v1_role.yaml | 32 + ...bpfman-operator.clusterserviceversion.yaml | 634 +++++++++- .../manifests/bpfman.io_bpfapplications.yaml | 10 +- .../bpfman.io_bpfnsapplications.yaml | 1125 +++++++++++++++++ bundle/manifests/bpfman.io_bpfnsprograms.yaml | 150 +++ bundle/manifests/bpfman.io_tcnsprograms.yaml | 447 +++++++ bundle/manifests/bpfman.io_tcprograms.yaml | 2 +- bundle/manifests/bpfman.io_tcxnsprograms.yaml | 424 +++++++ bundle/manifests/bpfman.io_tcxprograms.yaml | 4 +- .../manifests/bpfman.io_uprobensprograms.yaml | 417 ++++++ .../manifests/bpfman.io_uprobeprograms.yaml | 2 +- bundle/manifests/bpfman.io_xdpnsprograms.yaml | 429 +++++++ bundle/manifests/bpfman.io_xdpprograms.yaml | 2 +- cmd/bpfman-agent/main.go | 71 +- cmd/bpfman-operator/main.go | 72 +- .../crd/bases/bpfman.io_bpfapplications.yaml | 10 +- .../bases/bpfman.io_bpfnsapplications.yaml | 1119 ++++++++++++++++ config/crd/bases/bpfman.io_bpfnsprograms.yaml | 144 +++ config/crd/bases/bpfman.io_tcnsprograms.yaml | 441 +++++++ config/crd/bases/bpfman.io_tcprograms.yaml | 2 +- config/crd/bases/bpfman.io_tcxnsprograms.yaml | 418 ++++++ config/crd/bases/bpfman.io_tcxprograms.yaml | 4 +- .../crd/bases/bpfman.io_uprobensprograms.yaml | 411 ++++++ .../crd/bases/bpfman.io_uprobeprograms.yaml | 2 +- config/crd/bases/bpfman.io_xdpnsprograms.yaml | 423 +++++++ config/crd/bases/bpfman.io_xdpprograms.yaml | 2 +- config/crd/kustomization.yaml | 20 + .../cainjection_in_bpfnsapplications.yaml | 7 + .../patches/cainjection_in_bpfnsprograms.yaml | 7 + .../patches/cainjection_in_tcnsprograms.yaml | 7 + .../patches/cainjection_in_tcxnsprograms.yaml | 7 + .../patches/cainjection_in_tcxprograms.yaml | 7 + .../cainjection_in_uprobensprograms.yaml | 7 + .../patches/cainjection_in_xdpnsprograms.yaml | 7 + .../patches/webhook_in_bpfnsapplications.yaml | 16 + .../crd/patches/webhook_in_bpfnsprograms.yaml | 16 + .../crd/patches/webhook_in_tcnsprograms.yaml | 16 + .../crd/patches/webhook_in_tcxnsprograms.yaml | 16 + .../crd/patches/webhook_in_tcxprograms.yaml | 16 + .../patches/webhook_in_uprobensprograms.yaml | 16 + .../crd/patches/webhook_in_xdpnsprograms.yaml | 16 + ...bpfman-operator.clusterserviceversion.yaml | 42 +- config/rbac/bpfman-agent/role.yaml | 119 ++ config/rbac/bpfman-operator/role.yaml | 283 +++++ config/rbac/bpfnsapplication_editor_role.yaml | 31 + config/rbac/bpfnsapplication_viewer_role.yaml | 27 + config/rbac/bpfnsprogram_editor_role.yaml | 31 + config/rbac/bpfnsprogram_viewer_role.yaml | 27 + config/rbac/kustomization.yaml | 2 + config/rbac/role_binding.yaml | 20 + .../bpfman.io_v1alpha1_bpfnsapplication.yaml | 56 + ...pfman.io_v1alpha1_tc_pass_tcnsprogram.yaml | 33 + ...man.io_v1alpha1_tcx_pass_tcxnsprogram.yaml | 33 + ...an.io_v1alpha1_uprobe_uprobensprogram.yaml | 33 + ...man.io_v1alpha1_xdp_pass_xdpnsprogram.yaml | 32 + config/samples/kustomization.yaml | 16 +- .../bpfman-agent/application-ns-program.go | 288 +++++ .../application-ns-program_test.go | 364 ++++++ .../bpfman-agent/application-program.go | 73 +- .../bpfman-agent/application-program_test.go | 26 +- controllers/bpfman-agent/common.go | 309 +++-- controllers/bpfman-agent/common_cluster.go | 102 ++ controllers/bpfman-agent/common_namespace.go | 103 ++ controllers/bpfman-agent/containers.go | 31 +- controllers/bpfman-agent/fentry-program.go | 12 +- .../bpfman-agent/fentry-program_test.go | 17 +- controllers/bpfman-agent/fexit-program.go | 12 +- .../bpfman-agent/fexit-program_test.go | 16 +- .../bpfman-agent/internal/bpfman-core.go | 18 +- controllers/bpfman-agent/kprobe-program.go | 12 +- .../bpfman-agent/kprobe-program_test.go | 16 +- controllers/bpfman-agent/tc-ns-program.go | 299 +++++ .../bpfman-agent/tc-ns-program_test.go | 556 ++++++++ controllers/bpfman-agent/tc-program.go | 22 +- controllers/bpfman-agent/tc-program_test.go | 43 +- controllers/bpfman-agent/tcx-ns-program.go | 298 +++++ .../bpfman-agent/tcx-ns-program_test.go | 546 ++++++++ controllers/bpfman-agent/tcx-program.go | 20 +- controllers/bpfman-agent/tcx-program_test.go | 43 +- controllers/bpfman-agent/test_common.go | 21 +- .../bpfman-agent/tracepoint-program.go | 12 +- .../bpfman-agent/tracepoint-program_test.go | 18 +- controllers/bpfman-agent/uprobe-ns-program.go | 296 +++++ .../bpfman-agent/uprobe-ns-program_test.go | 243 ++++ controllers/bpfman-agent/uprobe-program.go | 20 +- .../bpfman-agent/uprobe-program_test.go | 32 +- controllers/bpfman-agent/xdp-ns-program.go | 295 +++++ .../bpfman-agent/xdp-ns-program_test.go | 390 ++++++ controllers/bpfman-agent/xdp-program.go | 22 +- controllers/bpfman-agent/xdp-program_test.go | 24 +- .../application-ns-program_test.go | 256 ++++ .../application-ns-programs.go | 141 +++ .../application-program_test.go | 8 +- .../bpfman-operator/application-programs.go | 13 +- controllers/bpfman-operator/common.go | 108 +- controllers/bpfman-operator/common_cluster.go | 83 ++ .../bpfman-operator/common_namespace.go | 84 ++ controllers/bpfman-operator/configmap.go | 2 +- controllers/bpfman-operator/configmap_test.go | 12 +- controllers/bpfman-operator/fentry-program.go | 12 +- .../bpfman-operator/fentry-program_test.go | 7 +- controllers/bpfman-operator/fexit-program.go | 12 +- .../bpfman-operator/fexit-program_test.go | 8 +- controllers/bpfman-operator/kprobe-program.go | 12 +- .../bpfman-operator/kprobe-program_test.go | 8 +- controllers/bpfman-operator/tc-ns-program.go | 134 ++ .../bpfman-operator/tc-ns-program_test.go | 176 +++ controllers/bpfman-operator/tc-program.go | 12 +- .../bpfman-operator/tc-program_test.go | 8 +- controllers/bpfman-operator/tcx-ns-program.go | 133 ++ .../bpfman-operator/tcx-ns-program_test.go | 172 +++ controllers/bpfman-operator/tcx-program.go | 12 +- .../bpfman-operator/tcx-program_test.go | 8 +- .../bpfman-operator/tracepoint-program.go | 12 +- .../tracepoint-program_test.go | 8 +- .../bpfman-operator/uprobe-ns-program.go | 133 ++ .../bpfman-operator/uprobe-ns-program_test.go | 171 +++ controllers/bpfman-operator/uprobe-program.go | 12 +- .../bpfman-operator/uprobe-program_test.go | 8 +- controllers/bpfman-operator/xdp-ns-program.go | 135 ++ .../bpfman-operator/xdp-ns-program_test.go | 175 +++ controllers/bpfman-operator/xdp-program.go | 12 +- .../bpfman-operator/xdp-program_test.go | 11 +- hack/namespace_scoped.sh | 71 ++ hack/namespace_scoped.yaml | 76 ++ hack/nginx-deployment.yaml | 1 + internal/constants.go | 104 +- internal/k8s.go | 18 + pkg/client/apis/v1alpha1/bpfnsapplication.go | 99 ++ pkg/client/apis/v1alpha1/bpfnsprogram.go | 99 ++ .../apis/v1alpha1/expansion_generated.go | 48 + pkg/client/apis/v1alpha1/tcnsprogram.go | 99 ++ pkg/client/apis/v1alpha1/tcxnsprogram.go | 99 ++ pkg/client/apis/v1alpha1/uprobensprogram.go | 99 ++ pkg/client/apis/v1alpha1/xdpnsprogram.go | 99 ++ .../typed/apis/v1alpha1/apis_client.go | 30 + .../typed/apis/v1alpha1/bpfnsapplication.go | 195 +++ .../typed/apis/v1alpha1/bpfnsprogram.go | 195 +++ .../apis/v1alpha1/fake/fake_apis_client.go | 24 + .../v1alpha1/fake/fake_bpfnsapplication.go | 141 +++ .../apis/v1alpha1/fake/fake_bpfnsprogram.go | 141 +++ .../apis/v1alpha1/fake/fake_tcnsprogram.go | 141 +++ .../apis/v1alpha1/fake/fake_tcxnsprogram.go | 141 +++ .../v1alpha1/fake/fake_uprobensprogram.go | 141 +++ .../apis/v1alpha1/fake/fake_xdpnsprogram.go | 141 +++ .../apis/v1alpha1/generated_expansion.go | 12 + .../typed/apis/v1alpha1/tcnsprogram.go | 195 +++ .../typed/apis/v1alpha1/tcxnsprogram.go | 195 +++ .../typed/apis/v1alpha1/uprobensprogram.go | 195 +++ .../typed/apis/v1alpha1/xdpnsprogram.go | 195 +++ .../apis/v1alpha1/bpfnsapplication.go | 90 ++ .../apis/v1alpha1/bpfnsprogram.go | 90 ++ .../apis/v1alpha1/interface.go | 42 + .../apis/v1alpha1/tcnsprogram.go | 90 ++ .../apis/v1alpha1/tcxnsprogram.go | 90 ++ .../apis/v1alpha1/uprobensprogram.go | 90 ++ .../apis/v1alpha1/xdpnsprogram.go | 90 ++ pkg/client/externalversions/generic.go | 12 + 180 files changed, 19038 insertions(+), 617 deletions(-) rename apis/v1alpha1/{bpfapplication_types.go => bpfApplication_types.go} (100%) create mode 100644 apis/v1alpha1/bpfNsApplication_types.go create mode 100644 apis/v1alpha1/bpfNsProgram_types.go rename apis/v1alpha1/{bpfprogram_types.go => bpfProgram_types.go} (80%) create mode 100644 apis/v1alpha1/tcNsProgram_types.go create mode 100644 apis/v1alpha1/tcxNsProgram_types.go create mode 100644 apis/v1alpha1/uprobeNsProgram_types.go create mode 100644 apis/v1alpha1/xdpNsProgram_types.go create mode 100644 bundle/manifests/bpfman-agent-role_rbac.authorization.k8s.io_v1_role.yaml create mode 100644 bundle/manifests/bpfman-bpfnsprogram-editor-role_rbac.authorization.k8s.io_v1_role.yaml create mode 100644 bundle/manifests/bpfman-bpfnsprogram-viewer-role_rbac.authorization.k8s.io_v1_role.yaml create mode 100644 bundle/manifests/bpfman-operator-role_rbac.authorization.k8s.io_v1_role.yaml create mode 100644 bundle/manifests/bpfman.io_bpfnsapplications.yaml create mode 100644 bundle/manifests/bpfman.io_bpfnsprograms.yaml create mode 100644 bundle/manifests/bpfman.io_tcnsprograms.yaml create mode 100644 bundle/manifests/bpfman.io_tcxnsprograms.yaml create mode 100644 bundle/manifests/bpfman.io_uprobensprograms.yaml create mode 100644 bundle/manifests/bpfman.io_xdpnsprograms.yaml create mode 100644 config/crd/bases/bpfman.io_bpfnsapplications.yaml create mode 100644 config/crd/bases/bpfman.io_bpfnsprograms.yaml create mode 100644 config/crd/bases/bpfman.io_tcnsprograms.yaml create mode 100644 config/crd/bases/bpfman.io_tcxnsprograms.yaml create mode 100644 config/crd/bases/bpfman.io_uprobensprograms.yaml create mode 100644 config/crd/bases/bpfman.io_xdpnsprograms.yaml create mode 100644 config/crd/patches/cainjection_in_bpfnsapplications.yaml create mode 100644 config/crd/patches/cainjection_in_bpfnsprograms.yaml create mode 100644 config/crd/patches/cainjection_in_tcnsprograms.yaml create mode 100644 config/crd/patches/cainjection_in_tcxnsprograms.yaml create mode 100644 config/crd/patches/cainjection_in_tcxprograms.yaml create mode 100644 config/crd/patches/cainjection_in_uprobensprograms.yaml create mode 100644 config/crd/patches/cainjection_in_xdpnsprograms.yaml create mode 100644 config/crd/patches/webhook_in_bpfnsapplications.yaml create mode 100644 config/crd/patches/webhook_in_bpfnsprograms.yaml create mode 100644 config/crd/patches/webhook_in_tcnsprograms.yaml create mode 100644 config/crd/patches/webhook_in_tcxnsprograms.yaml create mode 100644 config/crd/patches/webhook_in_tcxprograms.yaml create mode 100644 config/crd/patches/webhook_in_uprobensprograms.yaml create mode 100644 config/crd/patches/webhook_in_xdpnsprograms.yaml create mode 100644 config/rbac/bpfnsapplication_editor_role.yaml create mode 100644 config/rbac/bpfnsapplication_viewer_role.yaml create mode 100644 config/rbac/bpfnsprogram_editor_role.yaml create mode 100644 config/rbac/bpfnsprogram_viewer_role.yaml create mode 100644 config/samples/bpfman.io_v1alpha1_bpfnsapplication.yaml create mode 100644 config/samples/bpfman.io_v1alpha1_tc_pass_tcnsprogram.yaml create mode 100644 config/samples/bpfman.io_v1alpha1_tcx_pass_tcxnsprogram.yaml create mode 100644 config/samples/bpfman.io_v1alpha1_uprobe_uprobensprogram.yaml create mode 100644 config/samples/bpfman.io_v1alpha1_xdp_pass_xdpnsprogram.yaml create mode 100644 controllers/bpfman-agent/application-ns-program.go create mode 100644 controllers/bpfman-agent/application-ns-program_test.go create mode 100644 controllers/bpfman-agent/common_cluster.go create mode 100644 controllers/bpfman-agent/common_namespace.go create mode 100644 controllers/bpfman-agent/tc-ns-program.go create mode 100644 controllers/bpfman-agent/tc-ns-program_test.go create mode 100644 controllers/bpfman-agent/tcx-ns-program.go create mode 100644 controllers/bpfman-agent/tcx-ns-program_test.go create mode 100644 controllers/bpfman-agent/uprobe-ns-program.go create mode 100644 controllers/bpfman-agent/uprobe-ns-program_test.go create mode 100644 controllers/bpfman-agent/xdp-ns-program.go create mode 100644 controllers/bpfman-agent/xdp-ns-program_test.go create mode 100644 controllers/bpfman-operator/application-ns-program_test.go create mode 100644 controllers/bpfman-operator/application-ns-programs.go create mode 100644 controllers/bpfman-operator/common_cluster.go create mode 100644 controllers/bpfman-operator/common_namespace.go create mode 100644 controllers/bpfman-operator/tc-ns-program.go create mode 100644 controllers/bpfman-operator/tc-ns-program_test.go create mode 100644 controllers/bpfman-operator/tcx-ns-program.go create mode 100644 controllers/bpfman-operator/tcx-ns-program_test.go create mode 100644 controllers/bpfman-operator/uprobe-ns-program.go create mode 100644 controllers/bpfman-operator/uprobe-ns-program_test.go create mode 100644 controllers/bpfman-operator/xdp-ns-program.go create mode 100644 controllers/bpfman-operator/xdp-ns-program_test.go create mode 100755 hack/namespace_scoped.sh create mode 100644 hack/namespace_scoped.yaml create mode 100644 pkg/client/apis/v1alpha1/bpfnsapplication.go create mode 100644 pkg/client/apis/v1alpha1/bpfnsprogram.go create mode 100644 pkg/client/apis/v1alpha1/tcnsprogram.go create mode 100644 pkg/client/apis/v1alpha1/tcxnsprogram.go create mode 100644 pkg/client/apis/v1alpha1/uprobensprogram.go create mode 100644 pkg/client/apis/v1alpha1/xdpnsprogram.go create mode 100644 pkg/client/clientset/typed/apis/v1alpha1/bpfnsapplication.go create mode 100644 pkg/client/clientset/typed/apis/v1alpha1/bpfnsprogram.go create mode 100644 pkg/client/clientset/typed/apis/v1alpha1/fake/fake_bpfnsapplication.go create mode 100644 pkg/client/clientset/typed/apis/v1alpha1/fake/fake_bpfnsprogram.go create mode 100644 pkg/client/clientset/typed/apis/v1alpha1/fake/fake_tcnsprogram.go create mode 100644 pkg/client/clientset/typed/apis/v1alpha1/fake/fake_tcxnsprogram.go create mode 100644 pkg/client/clientset/typed/apis/v1alpha1/fake/fake_uprobensprogram.go create mode 100644 pkg/client/clientset/typed/apis/v1alpha1/fake/fake_xdpnsprogram.go create mode 100644 pkg/client/clientset/typed/apis/v1alpha1/tcnsprogram.go create mode 100644 pkg/client/clientset/typed/apis/v1alpha1/tcxnsprogram.go create mode 100644 pkg/client/clientset/typed/apis/v1alpha1/uprobensprogram.go create mode 100644 pkg/client/clientset/typed/apis/v1alpha1/xdpnsprogram.go create mode 100644 pkg/client/externalversions/apis/v1alpha1/bpfnsapplication.go create mode 100644 pkg/client/externalversions/apis/v1alpha1/bpfnsprogram.go create mode 100644 pkg/client/externalversions/apis/v1alpha1/tcnsprogram.go create mode 100644 pkg/client/externalversions/apis/v1alpha1/tcxnsprogram.go create mode 100644 pkg/client/externalversions/apis/v1alpha1/uprobensprogram.go create mode 100644 pkg/client/externalversions/apis/v1alpha1/xdpnsprogram.go diff --git a/PROJECT b/PROJECT index fea9a3abf..bf2c35e79 100644 --- a/PROJECT +++ b/PROJECT @@ -4,82 +4,129 @@ # More info: https://book.kubebuilder.io/reference/project-config.html domain: bpfman.io layout: - - go.kubebuilder.io/v3 +- go.kubebuilder.io/v3 plugins: manifests.sdk.operatorframework.io/v2: {} scorecard.sdk.operatorframework.io/v2: {} projectName: bpfman-operator repo: github.com/bpfman resources: - - api: - crdVersion: v1 - namespaced: true - controller: true - domain: bpfman.io - kind: BpfProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 - - api: - crdVersion: v1 - controller: true - domain: bpfman.io - kind: XdpProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 - - api: - crdVersion: v1 - controller: true - domain: bpfman.io - kind: TcProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 - - api: - crdVersion: v1 - controller: true - domain: bpfman.io - kind: TcxProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 - - api: - crdVersion: v1 - controller: true - domain: bpfman.io - kind: TracePointProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 - - api: - crdVersion: v1 - controller: true - domain: bpfman.io - kind: KprobeProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 - - api: - crdVersion: v1 - controller: true - domain: bpfman.io - kind: UprobeProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 - - api: - crdVersion: v1 - controller: true - domain: bpfman.io - kind: FentryProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 - - api: - crdVersion: v1 - controller: true - domain: bpfman.io - kind: FexitProgram - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 - - api: - crdVersion: v1 - controller: true - domain: bpfman.io - kind: BpfApplication - path: github.com/bpfman/bpfman-operator/apis/v1alpha1 - version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: BpfProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: XdpProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: TcProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: TcxProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: TracePointProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: KprobeProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: UprobeProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: FentryProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: FexitProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: bpfman.io + kind: BpfApplication + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: bpfman.io + kind: BpfNsProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: bpfman.io + kind: XdpNsProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: bpfman.io + kind: TcNsProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: bpfman.io + kind: TcxNsProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: bpfman.io + kind: UprobeNsProgram + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: bpfman.io + kind: BpfNsApplication + path: github.com/bpfman/bpfman-operator/apis/v1alpha1 + version: v1alpha1 version: "3" diff --git a/README.md b/README.md index 931e9dd19..81b39acdf 100644 --- a/README.md +++ b/README.md @@ -173,32 +173,48 @@ xdp-pass-all-nodes pass {} 0 {"primarynodein ## API Types Overview -Refer to [api-spec.md](https://bpfman.io/main/developer-guide/api-spec/) for a detailed description of all the bpfman Kubernetes API types. +Refer to [api-spec.md](https://bpfman.io/main/developer-guide/api-spec/) for a more detailed description of all the +bpfman Kubernetes API types. + +### Cluster Scoped Versus Namespaced Scoped CRDs + +For security reasons, cluster admins may want to limit certain applications to only loading eBPF programs +within a given namespace. +To provide these tighter controls on eBPF program loading, some of the bpfman Custom Resource Definitions (CRDs) +are Namespace scoped. +Not all eBPF programs make sense to be namespaced scoped. +The namespaced scoped CRDs use the "NsProgram" identifier and cluster scoped CRDs to use "Program" +identifier. ### Multiple Program CRDs -The multiple `*Program` CRDs are the bpfman Kubernetes API objects most relevant to users. -They express how and where eBPF programs are to be deployed within a Kubernetes cluster. Currently, -bpfman supports: +The multiple `*Program` CRDs are the bpfman Kubernetes API objects most relevant to users and can be used to +understand clusterwide state for an eBPF program. +It's designed to express how, and where eBPF programs are to be deployed within a Kubernetes cluster. +Currently bpfman supports: * `fentryProgram` * `fexitProgram` * `kprobeProgram` -* `tcProgram` +* `tcProgram` and `tcNsProgram` +* `tcxProgram` and `tcxNsProgram` * `tracepointProgram` -* `uprobeProgram` -* `xdpProgram` - -## BpfApplication CRD - -The `BpfApplication` CRD is designed for managing eBPF programs at an application level within a Kubernetes cluster. -This CRD allows Kubernetes users to define which eBPF programs are essential for an application's operations and specify -how these programs should be deployed across the cluster. - -## BpfProgram CRD - -The `BpfProgram` CRD is used internally by the `bpfman-deployment` to keep track of per-node `bpfman` state, -such as map pinpoints, and to report node-specific errors back to the user. +* `uprobeProgram` and `uprobeNsProgam` +* `xdpProgram` and `xdpNsProgram` + +There is also the `bpfApplication` and `bpfNsApplication` CRDs, which are +designed for managing eBPF programs at an application level within a Kubernetes cluster. +These CRD allows Kubernetes users to define which eBPF programs are essential for an application's operations +and specify how these programs should be deployed across the cluster. +With cluster scoped variant (`bpfApplication`), any variation of the cluster scoped +eBPF programs can be loaded. +With namespace scoped variant (`bpfNsApplication`), any variation of the namespace scoped +eBPF programs can be loaded. + +### BpfProgram and BpfNsProgram CRD + +The `BpfProgram` and `BpfNsProgram` CRDs are used internally by the bpfman-deployment to keep track of per +node bpfman state such as map pin points, and to report node specific errors back to the user. Kubernetes' users/controllers are only allowed to view these objects, NOT create or edit them. Applications wishing to use `bpfman` to deploy/manage their eBPF programs in Kubernetes will make use of this diff --git a/apis/v1alpha1/bpfapplication_types.go b/apis/v1alpha1/bpfApplication_types.go similarity index 100% rename from apis/v1alpha1/bpfapplication_types.go rename to apis/v1alpha1/bpfApplication_types.go diff --git a/apis/v1alpha1/bpfNsApplication_types.go b/apis/v1alpha1/bpfNsApplication_types.go new file mode 100644 index 000000000..acd78ba79 --- /dev/null +++ b/apis/v1alpha1/bpfNsApplication_types.go @@ -0,0 +1,97 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// BpfNsApplicationProgram defines the desired state of BpfApplication +// +union +// +kubebuilder:validation:XValidation:rule="has(self.type) && self.type == 'XDP' ? has(self.xdp) : !has(self.xdp)",message="xdp configuration is required when type is XDP, and forbidden otherwise" +// +kubebuilder:validation:XValidation:rule="has(self.type) && self.type == 'TC' ? has(self.tc) : !has(self.tc)",message="tc configuration is required when type is TC, and forbidden otherwise" +// +kubebuilder:validation:XValidation:rule="has(self.type) && self.type == 'TCX' ? has(self.tcx) : !has(self.tcx)",message="tcx configuration is required when type is TCX, and forbidden otherwise" +// +kubebuilder:validation:XValidation:rule="has(self.type) && self.type == 'Uprobe' ? has(self.uprobe) : !has(self.uprobe)",message="uprobe configuration is required when type is Uprobe, and forbidden otherwise" +// +kubebuilder:validation:XValidation:rule="has(self.type) && self.type == 'Uretprobe' ? has(self.uretprobe) : !has(self.uretprobe)",message="uretprobe configuration is required when type is Uretprobe, and forbidden otherwise" +type BpfNsApplicationProgram struct { + // Type specifies the bpf program type + // +unionDiscriminator + // +kubebuilder:validation:Required + // +kubebuilder:validation:Enum:="XDP";"TC";"TCX";"Uprobe";"Uretprobe" + Type EBPFProgType `json:"type,omitempty"` + + // xdp defines the desired state of the application's XdpNsPrograms. + // +unionMember + // +optional + XDP *XdpNsProgramInfo `json:"xdp,omitempty"` + + // tc defines the desired state of the application's TcNsPrograms. + // +unionMember + // +optional + TC *TcNsProgramInfo `json:"tc,omitempty"` + + // tcx defines the desired state of the application's TcxNsPrograms. + // +unionMember + // +optional + TCX *TcxNsProgramInfo `json:"tcx,omitempty"` + + // uprobe defines the desired state of the application's UprobeNsPrograms. + // +unionMember + // +optional + Uprobe *UprobeNsProgramInfo `json:"uprobe,omitempty"` + + // uretprobe defines the desired state of the application's UretprobeNsPrograms. + // +unionMember + // +optional + Uretprobe *UprobeNsProgramInfo `json:"uretprobe,omitempty"` +} + +// BpfApplicationSpec defines the desired state of BpfApplication +type BpfNsApplicationSpec struct { + BpfAppCommon `json:",inline"` + + // Programs is a list of bpf programs supported for a specific application. + // It's possible that the application can selectively choose which program(s) + // to run from this list. + // +kubebuilder:validation:MinItems:=1 + Programs []BpfNsApplicationProgram `json:"programs,omitempty"` +} + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Namespaced + +// BpfNsApplication is the Schema for the bpfapplications API +// +kubebuilder:printcolumn:name="NodeSelector",type=string,JSONPath=`.spec.nodeselector` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +type BpfNsApplication struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BpfNsApplicationSpec `json:"spec,omitempty"` + Status BpfApplicationStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// BpfNsApplicationList contains a list of BpfNsApplications +type BpfNsApplicationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []BpfNsApplication `json:"items"` +} diff --git a/apis/v1alpha1/bpfNsProgram_types.go b/apis/v1alpha1/bpfNsProgram_types.go new file mode 100644 index 000000000..58688fa3d --- /dev/null +++ b/apis/v1alpha1/bpfNsProgram_types.go @@ -0,0 +1,79 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// All fields are required unless explicitly marked optional +// +kubebuilder:validation:Required +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1types "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// BpfNsProgram is the Schema for the Bpfnsprograms API +// +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.type` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +type BpfNsProgram struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BpfProgramSpec `json:"spec"` + // +optional + Status BpfProgramStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// BpfNsProgramList contains a list of BpfProgram +type BpfNsProgramList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []BpfNsProgram `json:"items"` +} + +func (bp BpfNsProgram) GetName() string { + return bp.Name +} + +func (bp BpfNsProgram) GetUID() metav1types.UID { + return bp.UID +} + +func (bp BpfNsProgram) GetAnnotations() map[string]string { + return bp.Annotations +} + +func (bp BpfNsProgram) GetLabels() map[string]string { + return bp.Labels +} + +func (bp BpfNsProgram) GetStatus() *BpfProgramStatus { + return &bp.Status +} + +func (bp BpfNsProgram) GetClientObject() client.Object { + return &bp +} + +func (bpl BpfNsProgramList) GetItems() []BpfNsProgram { + return bpl.Items +} diff --git a/apis/v1alpha1/bpfprogram_types.go b/apis/v1alpha1/bpfProgram_types.go similarity index 80% rename from apis/v1alpha1/bpfprogram_types.go rename to apis/v1alpha1/bpfProgram_types.go index 9fa52c644..9e841bd44 100644 --- a/apis/v1alpha1/bpfprogram_types.go +++ b/apis/v1alpha1/bpfProgram_types.go @@ -20,6 +20,8 @@ package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1types "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" ) // +genclient @@ -69,3 +71,31 @@ type BpfProgramList struct { metav1.ListMeta `json:"metadata,omitempty"` Items []BpfProgram `json:"items"` } + +func (bp BpfProgram) GetName() string { + return bp.Name +} + +func (bp BpfProgram) GetUID() metav1types.UID { + return bp.UID +} + +func (bp BpfProgram) GetAnnotations() map[string]string { + return bp.Annotations +} + +func (bp BpfProgram) GetLabels() map[string]string { + return bp.Labels +} + +func (bp BpfProgram) GetStatus() *BpfProgramStatus { + return &bp.Status +} + +func (bp BpfProgram) GetClientObject() client.Object { + return &bp +} + +func (bpl BpfProgramList) GetItems() []BpfProgram { + return bpl.Items +} diff --git a/apis/v1alpha1/kprobeProgram_types.go b/apis/v1alpha1/kprobeProgram_types.go index b427e9c44..796a81da7 100644 --- a/apis/v1alpha1/kprobeProgram_types.go +++ b/apis/v1alpha1/kprobeProgram_types.go @@ -71,10 +71,6 @@ type KprobeProgramInfo struct { // +optional // +kubebuilder:default:=false RetProbe bool `json:"retprobe"` - - // // Host PID of container to attach the uprobe in. (Not supported yet by bpfman.) - // // +optional - // ContainerPid string `json:"containerpid"` } // KprobeProgramStatus defines the observed state of KprobeProgram diff --git a/apis/v1alpha1/shared_types.go b/apis/v1alpha1/shared_types.go index c31bd8eb4..7a6dca35f 100644 --- a/apis/v1alpha1/shared_types.go +++ b/apis/v1alpha1/shared_types.go @@ -53,6 +53,20 @@ type ContainerSelector struct { ContainerNames *[]string `json:"containernames,omitempty"` } +// ContainerNsSelector identifies a set of containers. It is different from ContainerSelector +// in that "Namespace" was removed. Namespace scoped programs can only attach to the namespace +// they are created in, so namespace at this level doesn't apply. +type ContainerNsSelector struct { + // Target pods. This field must be specified, to select all pods use + // standard metav1.LabelSelector semantics and make it empty. + Pods metav1.LabelSelector `json:"pods"` + + // Name(s) of container(s). If none are specified, all containers in the + // pod are selected. + // +optional + ContainerNames *[]string `json:"containernames,omitempty"` +} + // BpfProgramCommon defines the common attributes for all BPF programs type BpfProgramCommon struct { // BpfFunctionName is the name of the function that is the entry point for the BPF diff --git a/apis/v1alpha1/tcNsProgram_types.go b/apis/v1alpha1/tcNsProgram_types.go new file mode 100644 index 000000000..a8eca7372 --- /dev/null +++ b/apis/v1alpha1/tcNsProgram_types.go @@ -0,0 +1,90 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// All fields are required unless explicitly marked optional +// +kubebuilder:validation:Required +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Namespaced + +// TcNsProgram is the Schema for the TcNsProgram API +// +kubebuilder:printcolumn:name="BpfFunctionName",type=string,JSONPath=`.spec.bpffunctionname` +// +kubebuilder:printcolumn:name="NodeSelector",type=string,JSONPath=`.spec.nodeselector` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` +// +kubebuilder:printcolumn:name="Priority",type=string,JSONPath=`.spec.priority`,priority=1 +// +kubebuilder:printcolumn:name="Direction",type=string,JSONPath=`.spec.direction`,priority=1 +// +kubebuilder:printcolumn:name="InterfaceSelector",type=string,JSONPath=`.spec.interfaceselector`,priority=1 +// +kubebuilder:printcolumn:name="ProceedOn",type=string,JSONPath=`.spec.proceedon`,priority=1 +type TcNsProgram struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TcNsProgramSpec `json:"spec"` + // +optional + Status TcProgramStatus `json:"status,omitempty"` +} + +// TcNsProgramSpec defines the desired state of TcNsProgram +type TcNsProgramSpec struct { + TcNsProgramInfo `json:",inline"` + BpfAppCommon `json:",inline"` +} + +// TcNsProgramInfo defines the tc program details +type TcNsProgramInfo struct { + BpfProgramCommon `json:",inline"` + + // Selector to determine the network interface (or interfaces) + InterfaceSelector InterfaceSelector `json:"interfaceselector"` + + // Containers identifies the set of containers in which to attach the eBPF + // program. + Containers ContainerNsSelector `json:"containers"` + + // Priority specifies the priority of the tc program in relation to + // other programs of the same type with the same attach point. It is a value + // from 0 to 1000 where lower values have higher precedence. + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=1000 + Priority int32 `json:"priority"` + + // Direction specifies the direction of traffic the tc program should + // attach to for a given network device. + // +kubebuilder:validation:Enum=ingress;egress + Direction string `json:"direction"` + + // ProceedOn allows the user to call other tc programs in chain on this exit code. + // Multiple values are supported by repeating the parameter. + // +optional + // +kubebuilder:validation:MaxItems=11 + // +kubebuilder:default:={pipe,dispatcher_return} + ProceedOn []TcProceedOnValue `json:"proceedon"` +} + +// +kubebuilder:object:root=true +// TcNsProgramList contains a list of TcNsPrograms +type TcNsProgramList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []TcNsProgram `json:"items"` +} diff --git a/apis/v1alpha1/tcProgram_types.go b/apis/v1alpha1/tcProgram_types.go index 6db1481b7..519234214 100644 --- a/apis/v1alpha1/tcProgram_types.go +++ b/apis/v1alpha1/tcProgram_types.go @@ -61,7 +61,7 @@ type TcProgramInfo struct { // Selector to determine the network interface (or interfaces) InterfaceSelector InterfaceSelector `json:"interfaceselector"` - // Containers identifes the set of containers in which to attach the eBPF + // Containers identifies the set of containers in which to attach the eBPF // program. If Containers is not specified, the BPF program will be attached // in the root network namespace. // +optional diff --git a/apis/v1alpha1/tcxNsProgram_types.go b/apis/v1alpha1/tcxNsProgram_types.go new file mode 100644 index 000000000..61b8c8df3 --- /dev/null +++ b/apis/v1alpha1/tcxNsProgram_types.go @@ -0,0 +1,83 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// All fields are required unless explicitly marked optional +// +kubebuilder:validation:Required +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Namespaced + +// TcxNsProgram is the Schema for the TcxNsProgram API +// +kubebuilder:printcolumn:name="BpfFunctionName",type=string,JSONPath=`.spec.bpffunctionname` +// +kubebuilder:printcolumn:name="NodeSelector",type=string,JSONPath=`.spec.nodeselector` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` +// +kubebuilder:printcolumn:name="Direction",type=string,JSONPath=`.spec.direction`,priority=1 +// +kubebuilder:printcolumn:name="InterfaceSelector",type=string,JSONPath=`.spec.interfaceselector`,priority=1 +// +kubebuilder:printcolumn:name="Position",type=string,JSONPath=`.spec.position`,priority=1 +// +kubebuilder:printcolumn:name="Priority",type=string,JSONPath=`.spec.priority`,priority=1 +type TcxNsProgram struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TcxNsProgramSpec `json:"spec"` + // +optional + Status TcxProgramStatus `json:"status,omitempty"` +} + +// TcxNsProgramSpec defines the desired state of TcxNsProgram +type TcxNsProgramSpec struct { + TcxNsProgramInfo `json:",inline"` + BpfAppCommon `json:",inline"` +} + +// TcxNsProgramInfo defines the TCX Ns Program details +type TcxNsProgramInfo struct { + BpfProgramCommon `json:",inline"` + + // Selector to determine the network interface (or interfaces) + InterfaceSelector InterfaceSelector `json:"interfaceselector"` + + // Containers identifies the set of containers in which to attach the eBPF + // program. + Containers ContainerNsSelector `json:"containers"` + + // Direction specifies the direction of traffic the tcx program should + // attach to for a given network device. + // +kubebuilder:validation:Enum=ingress;egress + Direction string `json:"direction"` + + // Priority specifies the priority of the tc program in relation to + // other programs of the same type with the same attach point. It is a value + // from 0 to 1000 where lower values have higher precedence. + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=1000 + Priority int32 `json:"priority"` +} + +// +kubebuilder:object:root=true +// TcxNsProgramList contains a list of TcxNsPrograms +type TcxNsProgramList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []TcxNsProgram `json:"items"` +} diff --git a/apis/v1alpha1/tcxProgram_types.go b/apis/v1alpha1/tcxProgram_types.go index 6036ca8a4..e3de33c87 100644 --- a/apis/v1alpha1/tcxProgram_types.go +++ b/apis/v1alpha1/tcxProgram_types.go @@ -58,7 +58,7 @@ type TcxProgramInfo struct { // Selector to determine the network interface (or interfaces) InterfaceSelector InterfaceSelector `json:"interfaceselector"` - // Containers identifes the set of containers in which to attach the eBPF + // Containers identifies the set of containers in which to attach the eBPF // program. If Containers is not specified, the BPF program will be attached // in the root network namespace. // +optional @@ -77,7 +77,7 @@ type TcxProgramInfo struct { Priority int32 `json:"priority"` } -// TcxProgramStatus defines the observed state of TcProgram +// TcxProgramStatus defines the observed state of TcxProgram type TcxProgramStatus struct { BpfProgramStatusCommon `json:",inline"` } diff --git a/apis/v1alpha1/uprobeNsProgram_types.go b/apis/v1alpha1/uprobeNsProgram_types.go new file mode 100644 index 000000000..2b8dd21e0 --- /dev/null +++ b/apis/v1alpha1/uprobeNsProgram_types.go @@ -0,0 +1,95 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// All fields are required unless explicitly marked optional +// +kubebuilder:validation:Required +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Namespaced + +// UprobeNsProgram is the Schema for the UprobeNsPrograms API +// +kubebuilder:printcolumn:name="BpfFunctionName",type=string,JSONPath=`.spec.bpffunctionname` +// +kubebuilder:printcolumn:name="NodeSelector",type=string,JSONPath=`.spec.nodeselector` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` +// +kubebuilder:printcolumn:name="FunctionName",type=string,JSONPath=`.spec.func_name`,priority=1 +// +kubebuilder:printcolumn:name="Offset",type=integer,JSONPath=`.spec.offset`,priority=1 +// +kubebuilder:printcolumn:name="Target",type=string,JSONPath=`.spec.target`,priority=1 +// +kubebuilder:printcolumn:name="RetProbe",type=boolean,JSONPath=`.spec.retprobe`,priority=1 +// +kubebuilder:printcolumn:name="Pid",type=integer,JSONPath=`.spec.pid`,priority=1 +type UprobeNsProgram struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec UprobeNsProgramSpec `json:"spec"` + // +optional + Status UprobeProgramStatus `json:"status,omitempty"` +} + +// UprobeNsProgramSpec defines the desired state of UprobeProgram +type UprobeNsProgramSpec struct { + UprobeNsProgramInfo `json:",inline"` + BpfAppCommon `json:",inline"` +} + +// UprobeProgramInfo contains the information about the uprobe program +type UprobeNsProgramInfo struct { + BpfProgramCommon `json:",inline"` + + // Function to attach the uprobe to. + // +optional + FunctionName string `json:"func_name"` + + // Offset added to the address of the function for uprobe. + // +optional + // +kubebuilder:default:=0 + Offset uint64 `json:"offset"` + + // Library name or the absolute path to a binary or library. + Target string `json:"target"` + + // Whether the program is a uretprobe. Default is false + // +optional + // +kubebuilder:default:=false + RetProbe bool `json:"retprobe"` + + // Only execute uprobe for given process identification number (PID). If PID + // is not provided, uprobe executes for all PIDs. + // +optional + Pid int32 `json:"pid"` + + // Containers identifies the set of containers in which to attach the uprobe. + // If Containers is not specified, the uprobe will be attached in the + // bpfman-agent container. The ContainerNsSelector is very flexible and even + // allows the selection of all containers in a cluster. If an attempt is + // made to attach uprobes to too many containers, it can have a negative + // impact on on the cluster. + Containers ContainerNsSelector `json:"containers"` +} + +// +kubebuilder:object:root=true +// UprobeNsProgramList contains a list of UprobeNsPrograms +type UprobeNsProgramList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []UprobeNsProgram `json:"items"` +} diff --git a/apis/v1alpha1/uprobeProgram_types.go b/apis/v1alpha1/uprobeProgram_types.go index b98500aad..664401145 100644 --- a/apis/v1alpha1/uprobeProgram_types.go +++ b/apis/v1alpha1/uprobeProgram_types.go @@ -83,7 +83,7 @@ type UprobeProgramInfo struct { // +optional Pid int32 `json:"pid"` - // Containers identifes the set of containers in which to attach the uprobe. + // Containers identifies the set of containers in which to attach the uprobe. // If Containers is not specified, the uprobe will be attached in the // bpfman-agent container. The ContainerSelector is very flexible and even // allows the selection of all containers in a cluster. If an attempt is diff --git a/apis/v1alpha1/xdpNsProgram_types.go b/apis/v1alpha1/xdpNsProgram_types.go new file mode 100644 index 000000000..d0619c074 --- /dev/null +++ b/apis/v1alpha1/xdpNsProgram_types.go @@ -0,0 +1,83 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// All fields are required unless explicitly marked optional +// +kubebuilder:validation:Required +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Namespaced + +// XdpNsProgram is the Schema for the XdpNsPrograms API +// +kubebuilder:printcolumn:name="BpfFunctionName",type=string,JSONPath=`.spec.bpffunctionname` +// +kubebuilder:printcolumn:name="NodeSelector",type=string,JSONPath=`.spec.nodeselector` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` +// +kubebuilder:printcolumn:name="Priority",type=string,JSONPath=`.spec.priority`,priority=1 +// +kubebuilder:printcolumn:name="InterfaceSelector",type=string,JSONPath=`.spec.interfaceselector`,priority=1 +// +kubebuilder:printcolumn:name="ProceedOn",type=string,JSONPath=`.spec.proceedon`,priority=1 +type XdpNsProgram struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec XdpNsProgramSpec `json:"spec"` + // +optional + Status XdpProgramStatus `json:"status,omitempty"` +} + +// XdpNsProgramSpec defines the desired state of XdpNsProgram +type XdpNsProgramSpec struct { + XdpNsProgramInfo `json:",inline"` + BpfAppCommon `json:",inline"` +} + +// XdpNsProgramInfo defines the common fields for all XdpProgram types +type XdpNsProgramInfo struct { + BpfProgramCommon `json:",inline"` + // Selector to determine the network interface (or interfaces) + InterfaceSelector InterfaceSelector `json:"interfaceselector"` + + // Containers identifies the set of containers in which to attach the eBPF + // program. + Containers ContainerNsSelector `json:"containers"` + + // Priority specifies the priority of the bpf program in relation to + // other programs of the same type with the same attach point. It is a value + // from 0 to 1000 where lower values have higher precedence. + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=1000 + Priority int32 `json:"priority"` + + // ProceedOn allows the user to call other xdp programs in chain on this exit code. + // Multiple values are supported by repeating the parameter. + // +optional + // +kubebuilder:validation:MaxItems=6 + // +kubebuilder:default:={pass,dispatcher_return} + ProceedOn []XdpProceedOnValue `json:"proceedon"` +} + +// +kubebuilder:object:root=true +// XdpProgramList contains a list of XdpPrograms +type XdpNsProgramList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []XdpNsProgram `json:"items"` +} diff --git a/apis/v1alpha1/xdpProgram_types.go b/apis/v1alpha1/xdpProgram_types.go index 83f023989..f39c31981 100644 --- a/apis/v1alpha1/xdpProgram_types.go +++ b/apis/v1alpha1/xdpProgram_types.go @@ -59,7 +59,7 @@ type XdpProgramInfo struct { // Selector to determine the network interface (or interfaces) InterfaceSelector InterfaceSelector `json:"interfaceselector"` - // Containers identifes the set of containers in which to attach the eBPF + // Containers identifies the set of containers in which to attach the eBPF // program. If Containers is not specified, the BPF program will be attached // in the root network namespace. // +optional diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index 3954623d5..54af5b645 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -221,6 +221,187 @@ func (in *BpfApplicationStatus) DeepCopy() *BpfApplicationStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BpfNsApplication) DeepCopyInto(out *BpfNsApplication) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BpfNsApplication. +func (in *BpfNsApplication) DeepCopy() *BpfNsApplication { + if in == nil { + return nil + } + out := new(BpfNsApplication) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BpfNsApplication) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BpfNsApplicationList) DeepCopyInto(out *BpfNsApplicationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]BpfNsApplication, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BpfNsApplicationList. +func (in *BpfNsApplicationList) DeepCopy() *BpfNsApplicationList { + if in == nil { + return nil + } + out := new(BpfNsApplicationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BpfNsApplicationList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BpfNsApplicationProgram) DeepCopyInto(out *BpfNsApplicationProgram) { + *out = *in + if in.XDP != nil { + in, out := &in.XDP, &out.XDP + *out = new(XdpNsProgramInfo) + (*in).DeepCopyInto(*out) + } + if in.TC != nil { + in, out := &in.TC, &out.TC + *out = new(TcNsProgramInfo) + (*in).DeepCopyInto(*out) + } + if in.TCX != nil { + in, out := &in.TCX, &out.TCX + *out = new(TcxNsProgramInfo) + (*in).DeepCopyInto(*out) + } + if in.Uprobe != nil { + in, out := &in.Uprobe, &out.Uprobe + *out = new(UprobeNsProgramInfo) + (*in).DeepCopyInto(*out) + } + if in.Uretprobe != nil { + in, out := &in.Uretprobe, &out.Uretprobe + *out = new(UprobeNsProgramInfo) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BpfNsApplicationProgram. +func (in *BpfNsApplicationProgram) DeepCopy() *BpfNsApplicationProgram { + if in == nil { + return nil + } + out := new(BpfNsApplicationProgram) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BpfNsApplicationSpec) DeepCopyInto(out *BpfNsApplicationSpec) { + *out = *in + in.BpfAppCommon.DeepCopyInto(&out.BpfAppCommon) + if in.Programs != nil { + in, out := &in.Programs, &out.Programs + *out = make([]BpfNsApplicationProgram, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BpfNsApplicationSpec. +func (in *BpfNsApplicationSpec) DeepCopy() *BpfNsApplicationSpec { + if in == nil { + return nil + } + out := new(BpfNsApplicationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BpfNsProgram) DeepCopyInto(out *BpfNsProgram) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BpfNsProgram. +func (in *BpfNsProgram) DeepCopy() *BpfNsProgram { + if in == nil { + return nil + } + out := new(BpfNsProgram) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BpfNsProgram) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BpfNsProgramList) DeepCopyInto(out *BpfNsProgramList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]BpfNsProgram, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BpfNsProgramList. +func (in *BpfNsProgramList) DeepCopy() *BpfNsProgramList { + if in == nil { + return nil + } + out := new(BpfNsProgramList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BpfNsProgramList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BpfProgram) DeepCopyInto(out *BpfProgram) { *out = *in @@ -400,6 +581,31 @@ func (in *BytecodeSelector) DeepCopy() *BytecodeSelector { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerNsSelector) DeepCopyInto(out *ContainerNsSelector) { + *out = *in + in.Pods.DeepCopyInto(&out.Pods) + if in.ContainerNames != nil { + in, out := &in.ContainerNames, &out.ContainerNames + *out = new([]string) + if **in != nil { + in, out := *in, *out + *out = make([]string, len(*in)) + copy(*out, *in) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerNsSelector. +func (in *ContainerNsSelector) DeepCopy() *ContainerNsSelector { + if in == nil { + return nil + } + out := new(ContainerNsSelector) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ContainerSelector) DeepCopyInto(out *ContainerSelector) { *out = *in @@ -793,6 +999,105 @@ func (in *KprobeProgramStatus) DeepCopy() *KprobeProgramStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TcNsProgram) DeepCopyInto(out *TcNsProgram) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcNsProgram. +func (in *TcNsProgram) DeepCopy() *TcNsProgram { + if in == nil { + return nil + } + out := new(TcNsProgram) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TcNsProgram) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TcNsProgramInfo) DeepCopyInto(out *TcNsProgramInfo) { + *out = *in + in.BpfProgramCommon.DeepCopyInto(&out.BpfProgramCommon) + in.InterfaceSelector.DeepCopyInto(&out.InterfaceSelector) + in.Containers.DeepCopyInto(&out.Containers) + if in.ProceedOn != nil { + in, out := &in.ProceedOn, &out.ProceedOn + *out = make([]TcProceedOnValue, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcNsProgramInfo. +func (in *TcNsProgramInfo) DeepCopy() *TcNsProgramInfo { + if in == nil { + return nil + } + out := new(TcNsProgramInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TcNsProgramList) DeepCopyInto(out *TcNsProgramList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TcNsProgram, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcNsProgramList. +func (in *TcNsProgramList) DeepCopy() *TcNsProgramList { + if in == nil { + return nil + } + out := new(TcNsProgramList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TcNsProgramList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TcNsProgramSpec) DeepCopyInto(out *TcNsProgramSpec) { + *out = *in + in.TcNsProgramInfo.DeepCopyInto(&out.TcNsProgramInfo) + in.BpfAppCommon.DeepCopyInto(&out.BpfAppCommon) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcNsProgramSpec. +func (in *TcNsProgramSpec) DeepCopy() *TcNsProgramSpec { + if in == nil { + return nil + } + out := new(TcNsProgramSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TcProgram) DeepCopyInto(out *TcProgram) { *out = *in @@ -912,6 +1217,100 @@ func (in *TcProgramStatus) DeepCopy() *TcProgramStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TcxNsProgram) DeepCopyInto(out *TcxNsProgram) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcxNsProgram. +func (in *TcxNsProgram) DeepCopy() *TcxNsProgram { + if in == nil { + return nil + } + out := new(TcxNsProgram) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TcxNsProgram) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TcxNsProgramInfo) DeepCopyInto(out *TcxNsProgramInfo) { + *out = *in + in.BpfProgramCommon.DeepCopyInto(&out.BpfProgramCommon) + in.InterfaceSelector.DeepCopyInto(&out.InterfaceSelector) + in.Containers.DeepCopyInto(&out.Containers) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcxNsProgramInfo. +func (in *TcxNsProgramInfo) DeepCopy() *TcxNsProgramInfo { + if in == nil { + return nil + } + out := new(TcxNsProgramInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TcxNsProgramList) DeepCopyInto(out *TcxNsProgramList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TcxNsProgram, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcxNsProgramList. +func (in *TcxNsProgramList) DeepCopy() *TcxNsProgramList { + if in == nil { + return nil + } + out := new(TcxNsProgramList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TcxNsProgramList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TcxNsProgramSpec) DeepCopyInto(out *TcxNsProgramSpec) { + *out = *in + in.TcxNsProgramInfo.DeepCopyInto(&out.TcxNsProgramInfo) + in.BpfAppCommon.DeepCopyInto(&out.BpfAppCommon) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcxNsProgramSpec. +func (in *TcxNsProgramSpec) DeepCopy() *TcxNsProgramSpec { + if in == nil { + return nil + } + out := new(TcxNsProgramSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TcxProgram) DeepCopyInto(out *TcxProgram) { *out = *in @@ -1139,6 +1538,99 @@ func (in *TracepointProgramStatus) DeepCopy() *TracepointProgramStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UprobeNsProgram) DeepCopyInto(out *UprobeNsProgram) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UprobeNsProgram. +func (in *UprobeNsProgram) DeepCopy() *UprobeNsProgram { + if in == nil { + return nil + } + out := new(UprobeNsProgram) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UprobeNsProgram) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UprobeNsProgramInfo) DeepCopyInto(out *UprobeNsProgramInfo) { + *out = *in + in.BpfProgramCommon.DeepCopyInto(&out.BpfProgramCommon) + in.Containers.DeepCopyInto(&out.Containers) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UprobeNsProgramInfo. +func (in *UprobeNsProgramInfo) DeepCopy() *UprobeNsProgramInfo { + if in == nil { + return nil + } + out := new(UprobeNsProgramInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UprobeNsProgramList) DeepCopyInto(out *UprobeNsProgramList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]UprobeNsProgram, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UprobeNsProgramList. +func (in *UprobeNsProgramList) DeepCopy() *UprobeNsProgramList { + if in == nil { + return nil + } + out := new(UprobeNsProgramList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UprobeNsProgramList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UprobeNsProgramSpec) DeepCopyInto(out *UprobeNsProgramSpec) { + *out = *in + in.UprobeNsProgramInfo.DeepCopyInto(&out.UprobeNsProgramInfo) + in.BpfAppCommon.DeepCopyInto(&out.BpfAppCommon) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UprobeNsProgramSpec. +func (in *UprobeNsProgramSpec) DeepCopy() *UprobeNsProgramSpec { + if in == nil { + return nil + } + out := new(UprobeNsProgramSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UprobeProgram) DeepCopyInto(out *UprobeProgram) { *out = *in @@ -1252,6 +1744,105 @@ func (in *UprobeProgramStatus) DeepCopy() *UprobeProgramStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *XdpNsProgram) DeepCopyInto(out *XdpNsProgram) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new XdpNsProgram. +func (in *XdpNsProgram) DeepCopy() *XdpNsProgram { + if in == nil { + return nil + } + out := new(XdpNsProgram) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *XdpNsProgram) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *XdpNsProgramInfo) DeepCopyInto(out *XdpNsProgramInfo) { + *out = *in + in.BpfProgramCommon.DeepCopyInto(&out.BpfProgramCommon) + in.InterfaceSelector.DeepCopyInto(&out.InterfaceSelector) + in.Containers.DeepCopyInto(&out.Containers) + if in.ProceedOn != nil { + in, out := &in.ProceedOn, &out.ProceedOn + *out = make([]XdpProceedOnValue, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new XdpNsProgramInfo. +func (in *XdpNsProgramInfo) DeepCopy() *XdpNsProgramInfo { + if in == nil { + return nil + } + out := new(XdpNsProgramInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *XdpNsProgramList) DeepCopyInto(out *XdpNsProgramList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]XdpNsProgram, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new XdpNsProgramList. +func (in *XdpNsProgramList) DeepCopy() *XdpNsProgramList { + if in == nil { + return nil + } + out := new(XdpNsProgramList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *XdpNsProgramList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *XdpNsProgramSpec) DeepCopyInto(out *XdpNsProgramSpec) { + *out = *in + in.XdpNsProgramInfo.DeepCopyInto(&out.XdpNsProgramInfo) + in.BpfAppCommon.DeepCopyInto(&out.BpfAppCommon) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new XdpNsProgramSpec. +func (in *XdpNsProgramSpec) DeepCopy() *XdpNsProgramSpec { + if in == nil { + return nil + } + out := new(XdpNsProgramSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *XdpProgram) DeepCopyInto(out *XdpProgram) { *out = *in diff --git a/apis/v1alpha1/zz_generated.register.go b/apis/v1alpha1/zz_generated.register.go index 5b55645af..569ccddcd 100644 --- a/apis/v1alpha1/zz_generated.register.go +++ b/apis/v1alpha1/zz_generated.register.go @@ -63,6 +63,10 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &BpfApplication{}, &BpfApplicationList{}, + &BpfNsApplication{}, + &BpfNsApplicationList{}, + &BpfNsProgram{}, + &BpfNsProgramList{}, &BpfProgram{}, &BpfProgramList{}, &FentryProgram{}, @@ -71,14 +75,22 @@ func addKnownTypes(scheme *runtime.Scheme) error { &FexitProgramList{}, &KprobeProgram{}, &KprobeProgramList{}, + &TcNsProgram{}, + &TcNsProgramList{}, &TcProgram{}, &TcProgramList{}, + &TcxNsProgram{}, + &TcxNsProgramList{}, &TcxProgram{}, &TcxProgramList{}, &TracepointProgram{}, &TracepointProgramList{}, + &UprobeNsProgram{}, + &UprobeNsProgramList{}, &UprobeProgram{}, &UprobeProgramList{}, + &XdpNsProgram{}, + &XdpNsProgramList{}, &XdpProgram{}, &XdpProgramList{}, ) diff --git a/bundle/manifests/bpfman-agent-role_rbac.authorization.k8s.io_v1_clusterrole.yaml b/bundle/manifests/bpfman-agent-role_rbac.authorization.k8s.io_v1_clusterrole.yaml index feb710832..40fc33a2c 100644 --- a/bundle/manifests/bpfman-agent-role_rbac.authorization.k8s.io_v1_clusterrole.yaml +++ b/bundle/manifests/bpfman-agent-role_rbac.authorization.k8s.io_v1_clusterrole.yaml @@ -18,6 +18,40 @@ rules: - bpfapplications/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -86,6 +120,14 @@ rules: - kprobeprograms/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - tcnsprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: @@ -100,6 +142,14 @@ rules: - tcprograms/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: @@ -128,6 +178,14 @@ rules: - tracepointprograms/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - uprobensprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: @@ -142,6 +200,14 @@ rules: - uprobeprograms/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: diff --git a/bundle/manifests/bpfman-agent-role_rbac.authorization.k8s.io_v1_role.yaml b/bundle/manifests/bpfman-agent-role_rbac.authorization.k8s.io_v1_role.yaml new file mode 100644 index 000000000..eecf32a61 --- /dev/null +++ b/bundle/manifests/bpfman-agent-role_rbac.authorization.k8s.io_v1_role.yaml @@ -0,0 +1,52 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + name: bpfman-agent-role +rules: +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - tcnsprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - uprobensprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms/finalizers + verbs: + - update diff --git a/bundle/manifests/bpfman-bpfnsprogram-editor-role_rbac.authorization.k8s.io_v1_role.yaml b/bundle/manifests/bpfman-bpfnsprogram-editor-role_rbac.authorization.k8s.io_v1_role.yaml new file mode 100644 index 000000000..6bd7cf7cd --- /dev/null +++ b/bundle/manifests/bpfman-bpfnsprogram-editor-role_rbac.authorization.k8s.io_v1_role.yaml @@ -0,0 +1,31 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: bpfman-operator + app.kubernetes.io/instance: bpfnsprogram-editor-role + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: role + app.kubernetes.io/part-of: bpfman-operator + name: bpfman-bpfnsprogram-editor-role +rules: +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms/status + verbs: + - get diff --git a/bundle/manifests/bpfman-bpfnsprogram-viewer-role_rbac.authorization.k8s.io_v1_role.yaml b/bundle/manifests/bpfman-bpfnsprogram-viewer-role_rbac.authorization.k8s.io_v1_role.yaml new file mode 100644 index 000000000..9d83ad2e5 --- /dev/null +++ b/bundle/manifests/bpfman-bpfnsprogram-viewer-role_rbac.authorization.k8s.io_v1_role.yaml @@ -0,0 +1,27 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: bpfman-operator + app.kubernetes.io/instance: bpfnsprogram-viewer-role + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: role + app.kubernetes.io/part-of: bpfman-operator + name: bpfman-bpfnsprogram-viewer-role +rules: +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms/status + verbs: + - get diff --git a/bundle/manifests/bpfman-operator-role_rbac.authorization.k8s.io_v1_role.yaml b/bundle/manifests/bpfman-operator-role_rbac.authorization.k8s.io_v1_role.yaml new file mode 100644 index 000000000..4dcbda1fc --- /dev/null +++ b/bundle/manifests/bpfman-operator-role_rbac.authorization.k8s.io_v1_role.yaml @@ -0,0 +1,32 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + name: bpfman-operator-role +rules: +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms/status + verbs: + - get + - patch + - update diff --git a/bundle/manifests/bpfman-operator.clusterserviceversion.yaml b/bundle/manifests/bpfman-operator.clusterserviceversion.yaml index 5e79d542d..bf1a4e6bc 100644 --- a/bundle/manifests/bpfman-operator.clusterserviceversion.yaml +++ b/bundle/manifests/bpfman-operator.clusterserviceversion.yaml @@ -95,6 +95,96 @@ metadata: ] } }, + { + "apiVersion": "bpfman.io/v1alpha1", + "kind": "BpfNsApplication", + "metadata": { + "labels": { + "app.kubernetes.io/name": "bpfnsapplication" + }, + "name": "bpfapplication-sample", + "namespace": "acme" + }, + "spec": { + "bytecode": { + "image": { + "url": "quay.io/bpfman-bytecode/go-app-counter:latest" + } + }, + "nodeselector": {}, + "programs": [ + { + "tc": { + "bpffunctionname": "stats", + "containers": { + "pods": { + "matchLabels": { + "app": "nginx" + } + } + }, + "direction": "ingress", + "interfaceselector": { + "primarynodeinterface": true + }, + "priority": 55 + }, + "type": "TC" + }, + { + "tcx": { + "bpffunctionname": "tcx_stats", + "containers": { + "pods": { + "matchLabels": { + "app": "nginx" + } + } + }, + "direction": "ingress", + "interfaceselector": { + "primarynodeinterface": true + }, + "priority": 500 + }, + "type": "TCX" + }, + { + "type": "Uprobe", + "uprobe": { + "bpffunctionname": "uprobe_counter", + "containers": { + "pods": { + "matchLabels": { + "app": "nginx" + } + } + }, + "func_name": "malloc", + "retprobe": false, + "target": "libc" + } + }, + { + "type": "XDP", + "xdp": { + "bpffunctionname": "xdp_stats", + "containers": { + "pods": { + "matchLabels": { + "app": "nginx" + } + } + }, + "interfaceselector": { + "primarynodeinterface": true + }, + "priority": 55 + } + } + ] + } + }, { "apiVersion": "bpfman.io/v1alpha1", "kind": "FentryProgram", @@ -168,6 +258,54 @@ metadata: "retprobe": false } }, + { + "apiVersion": "bpfman.io/v1alpha1", + "kind": "TcNsProgram", + "metadata": { + "labels": { + "app.kubernetes.io/name": "tcnsprogram" + }, + "name": "tc-containers", + "namespace": "acme" + }, + "spec": { + "bpffunctionname": "pass", + "bytecode": { + "image": { + "url": "quay.io/bpfman-bytecode/tc_pass:latest" + } + }, + "containers": { + "containernames": [ + "nginx" + ], + "pods": { + "matchLabels": { + "app": "nginx" + } + } + }, + "direction": "ingress", + "globaldata": { + "GLOBAL_u32": [ + 13, + 12, + 11, + 10 + ], + "GLOBAL_u8": [ + 1 + ] + }, + "interfaceselector": { + "interfaces": [ + "eth0" + ] + }, + "nodeselector": {}, + "priority": 0 + } + }, { "apiVersion": "bpfman.io/v1alpha1", "kind": "TcProgram", @@ -203,6 +341,89 @@ metadata: "priority": 0 } }, + { + "apiVersion": "bpfman.io/v1alpha1", + "kind": "TcxNsProgram", + "metadata": { + "labels": { + "app.kubernetes.io/name": "tcxnsprogram" + }, + "name": "tcx-containers", + "namespace": "acme" + }, + "spec": { + "bpffunctionname": "tcx_next", + "bytecode": { + "image": { + "url": "quay.io/bpfman-bytecode/tcx_test:latest" + } + }, + "containers": { + "containernames": [ + "nginx" + ], + "pods": { + "matchLabels": { + "app": "nginx" + } + } + }, + "direction": "ingress", + "globaldata": { + "GLOBAL_u32": [ + 13, + 12, + 11, + 10 + ], + "GLOBAL_u8": [ + 1 + ] + }, + "interfaceselector": { + "interfaces": [ + "eth0" + ] + }, + "nodeselector": {}, + "priority": 0 + } + }, + { + "apiVersion": "bpfman.io/v1alpha1", + "kind": "TcxProgram", + "metadata": { + "labels": { + "app.kubernetes.io/name": "tcxprogram" + }, + "name": "tcx-pass-all-nodes" + }, + "spec": { + "bpffunctionname": "tcx_pass", + "bytecode": { + "image": { + "url": "quay.io/bpfman-bytecode/tcx_test:latest" + } + }, + "direction": "ingress", + "globaldata": { + "GLOBAL_u32": [ + 13, + 12, + 11, + 10 + ], + "GLOBAL_u8": [ + 1 + ] + }, + "interfaceselector": { + "primarynodeinterface": true + }, + "nodeselector": {}, + "priority": 0 + } + }, { "apiVersion": "bpfman.io/v1alpha1", "kind": "TracepointProgram", @@ -236,6 +457,50 @@ metadata: "nodeselector": {} } }, + { + "apiVersion": "bpfman.io/v1alpha1", + "kind": "UprobeNsProgram", + "metadata": { + "labels": { + "app.kubernetes.io/name": "uprobensprogram" + }, + "name": "uprobe-example", + "namespace": "acme" + }, + "spec": { + "bpffunctionname": "my_uprobe", + "bytecode": { + "image": { + "url": "quay.io/bpfman-bytecode/uprobe:latest" + } + }, + "containers": { + "containernames": [ + "nginx" + ], + "pods": { + "matchLabels": { + "app": "nginx" + } + } + }, + "func_name": "syscall", + "globaldata": { + "GLOBAL_u32": [ + 13, + 12, + 11, + 10 + ], + "GLOBAL_u8": [ + 1 + ] + }, + "nodeselector": {}, + "retprobe": false, + "target": "libc" + } + }, { "apiVersion": "bpfman.io/v1alpha1", "kind": "UprobeProgram", @@ -269,6 +534,53 @@ metadata: "target": "libc" } }, + { + "apiVersion": "bpfman.io/v1alpha1", + "kind": "XdpNsProgram", + "metadata": { + "labels": { + "app.kubernetes.io/name": "xdpnsprogram" + }, + "name": "xdp-ns-pass-all-nodes", + "namespace": "acme" + }, + "spec": { + "bpffunctionname": "pass", + "bytecode": { + "image": { + "url": "quay.io/bpfman-bytecode/xdp_pass:latest" + } + }, + "containers": { + "containernames": [ + "nginx" + ], + "pods": { + "matchLabels": { + "app": "nginx" + } + } + }, + "globaldata": { + "GLOBAL_u32": [ + 13, + 12, + 11, + 10 + ], + "GLOBAL_u8": [ + 1 + ] + }, + "interfaceselector": { + "interfaces": [ + "eth0" + ] + }, + "nodeselector": {}, + "priority": 0 + } + }, { "apiVersion": "bpfman.io/v1alpha1", "kind": "XdpProgram", @@ -307,7 +619,7 @@ metadata: capabilities: Basic Install categories: OpenShift Optional containerImage: quay.io/bpfman/bpfman-operator:latest - createdAt: "2024-12-06T14:27:05Z" + createdAt: "2024-12-19T02:32:37Z" features.operators.openshift.io/cnf: "false" features.operators.openshift.io/cni: "false" features.operators.openshift.io/csi: "true" @@ -357,6 +669,16 @@ spec: kind: BpfApplication name: bpfapplications.bpfman.io version: v1alpha1 + - description: BpfNsApplication is the Schema for the BpfNsApplications API + displayName: Bpf Namespaced Application + kind: BpfNsApplication + name: bpfnsapplications.bpfman.io + version: v1alpha1 + - description: BpfNsProgram is the Schema for the BpfNsProgram API + displayName: Bpf Namespaced Program + kind: BpfNsProgram + name: bpfnsprograms.bpfman.io + version: v1alpha1 - description: BpfProgram is the Schema for the BpfProgram API displayName: Bpf Program kind: BpfProgram @@ -377,12 +699,24 @@ spec: kind: KprobeProgram name: kprobeprograms.bpfman.io version: v1alpha1 + - description: TcNsProgram is the Schema for the Tcnsprograms API + displayName: TC Namespaced Program + kind: TcNsProgram + name: tcnsprograms.bpfman.io + version: v1alpha1 - description: TcProgram is the Schema for the Tcprograms API - displayName: Tc Program + displayName: TC Program kind: TcProgram name: tcprograms.bpfman.io version: v1alpha1 - - kind: TcxProgram + - description: TcxNsProgram is the Schema for the Tcxnsprograms API + displayName: TCX Namespaced Program + kind: TcxNsProgram + name: tcxnsprograms.bpfman.io + version: v1alpha1 + - description: TcxProgram is the Schema for the Tcxprograms API + displayName: TCX Program + kind: TcxProgram name: tcxprograms.bpfman.io version: v1alpha1 - description: TracepointProgram is the Schema for the Tracepointprograms API @@ -390,11 +724,21 @@ spec: kind: TracepointProgram name: tracepointprograms.bpfman.io version: v1alpha1 + - description: UprobeNsProgram is the Schema for the Uprobensprograms API + displayName: Uprobe Namespaced Program + kind: UprobeNsProgram + name: uprobensprograms.bpfman.io + version: v1alpha1 - description: UprobeProgram is the Schema for the Uprobeprograms API displayName: Uprobe Program kind: UprobeProgram name: uprobeprograms.bpfman.io version: v1alpha1 + - description: XdpNsProgram is the Schema for the Xdpnsprograms API + displayName: Xdp Namespaced Program + kind: XdpNsProgram + name: xdpnsprograms.bpfman.io + version: v1alpha1 - description: XdpProgram is the Schema for the Xdpprograms API displayName: Xdp Program kind: XdpProgram @@ -413,9 +757,11 @@ spec: `warn`, `error`, and `fatal`, defaults to `info`\n- `bpfman.agent.log.level`: the log level for the bpfman-agent currently supports `info`, `debug`, and `trace` \n\nThe bpfman operator deploys eBPF programs via CRDs. The following CRDs are - currently available, \n\n- BpfApplication\n- XdpProgram\n- TcProgram\n - TracepointProgram\n- - KprobeProgram\n- UprobeProgram\n- FentryProgram\n- FexitProgram\n\n ## More information\n\nPlease - checkout the [bpfman community website](https://bpfman.io/) for more information." + currently available, \n\n- BpfApplication\n- FentryProgram\n- FexitProgram\n - + KprobeProgram\n- TcProgram\n- TcxProgram\n- TracepointProgram\n- UprobeProgram\n- + XdpProgram\n - BpfNsApplication\n- TcProgram\n- TcxNsProgram\n- UprobeNsProgram\n- + XdpNsProgram\n\n ## More information\n\nPlease checkout the [bpfman community + website](https://bpfman.io/) for more information." displayName: Bpfman Operator icon: - base64data: | @@ -759,6 +1105,40 @@ spec: - get - patch - update + - apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - bpfnsapplications/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - bpfnsapplications/status + verbs: + - get + - patch + - update + - apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: @@ -851,6 +1231,32 @@ spec: - get - patch - update + - apiGroups: + - bpfman.io + resources: + - tcnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - tcnsprograms/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - tcnsprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -877,6 +1283,32 @@ spec: - get - patch - update + - apiGroups: + - bpfman.io + resources: + - tcxnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - tcxnsprograms/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - tcxnsprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -929,6 +1361,32 @@ spec: - get - patch - update + - apiGroups: + - bpfman.io + resources: + - uprobensprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - uprobensprograms/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - uprobensprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -955,6 +1413,32 @@ spec: - get - patch - update + - apiGroups: + - bpfman.io + resources: + - xdpnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - xdpnsprograms/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - xdpnsprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -1167,6 +1651,144 @@ spec: verbs: - create - patch + - apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - bpfnsapplications/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - bpfnsapplications/status + verbs: + - get + - patch + - update + - apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - get + - list + - watch + - apiGroups: + - bpfman.io + resources: + - tcnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - tcnsprograms/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - tcnsprograms/status + verbs: + - get + - patch + - update + - apiGroups: + - bpfman.io + resources: + - tcxnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - tcxnsprograms/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - tcxnsprograms/status + verbs: + - get + - patch + - update + - apiGroups: + - bpfman.io + resources: + - uprobensprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - uprobensprograms/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - uprobensprograms/status + verbs: + - get + - patch + - update + - apiGroups: + - bpfman.io + resources: + - xdpnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - xdpnsprograms/finalizers + verbs: + - update + - apiGroups: + - bpfman.io + resources: + - xdpnsprograms/status + verbs: + - get + - patch + - update serviceAccountName: bpfman-operator strategy: deployment installModes: diff --git a/bundle/manifests/bpfman.io_bpfapplications.yaml b/bundle/manifests/bpfman.io_bpfapplications.yaml index cd8cc45cd..fde48711d 100644 --- a/bundle/manifests/bpfman.io_bpfapplications.yaml +++ b/bundle/manifests/bpfman.io_bpfapplications.yaml @@ -461,7 +461,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: @@ -652,7 +652,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: @@ -904,7 +904,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the uprobe. + Containers identifies the set of containers in which to attach the uprobe. If Containers is not specified, the uprobe will be attached in the bpfman-agent container. The ContainerSelector is very flexible and even allows the selection of all containers in a cluster. If an attempt is @@ -1062,7 +1062,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the uprobe. + Containers identifies the set of containers in which to attach the uprobe. If Containers is not specified, the uprobe will be attached in the bpfman-agent container. The ContainerSelector is very flexible and even allows the selection of all containers in a cluster. If an attempt is @@ -1220,7 +1220,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: diff --git a/bundle/manifests/bpfman.io_bpfnsapplications.yaml b/bundle/manifests/bpfman.io_bpfnsapplications.yaml new file mode 100644 index 000000000..9a355155b --- /dev/null +++ b/bundle/manifests/bpfman.io_bpfnsapplications.yaml @@ -0,0 +1,1125 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + creationTimestamp: null + name: bpfnsapplications.bpfman.io +spec: + group: bpfman.io + names: + kind: BpfNsApplication + listKind: BpfNsApplicationList + plural: bpfnsapplications + singular: bpfnsapplication + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: BpfNsApplication is the Schema for the bpfapplications API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BpfApplicationSpec defines the desired state of BpfApplication + properties: + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + programs: + description: |- + Programs is a list of bpf programs supported for a specific application. + It's possible that the application can selectively choose which program(s) + to run from this list. + items: + description: BpfNsApplicationProgram defines the desired state of + BpfApplication + properties: + tc: + description: tc defines the desired state of the application's + TcNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object + direction: + description: |- + Direction specifies the direction of traffic the tc program should + attach to for a given network device. + enum: + - ingress + - egress + type: string + interfaceselector: + description: Selector to determine the network interface + (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface + on the node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + priority: + description: |- + Priority specifies the priority of the tc program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + proceedon: + default: + - pipe + - dispatcher_return + description: |- + ProceedOn allows the user to call other tc programs in chain on this exit code. + Multiple values are supported by repeating the parameter. + items: + enum: + - unspec + - ok + - reclassify + - shot + - pipe + - stolen + - queued + - repeat + - redirect + - trap + - dispatcher_return + type: string + maxItems: 11 + type: array + required: + - bpffunctionname + - containers + - direction + - interfaceselector + - priority + type: object + tcx: + description: tcx defines the desired state of the application's + TcxNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object + direction: + description: |- + Direction specifies the direction of traffic the tcx program should + attach to for a given network device. + enum: + - ingress + - egress + type: string + interfaceselector: + description: Selector to determine the network interface + (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface + on the node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + priority: + description: |- + Priority specifies the priority of the tc program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + required: + - bpffunctionname + - containers + - direction + - interfaceselector + - priority + type: object + type: + description: Type specifies the bpf program type + enum: + - XDP + - TC + - TCX + - Uprobe + - Uretprobe + type: string + uprobe: + description: uprobe defines the desired state of the application's + UprobeNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the uprobe. + If Containers is not specified, the uprobe will be attached in the + bpfman-agent container. The ContainerNsSelector is very flexible and even + allows the selection of all containers in a cluster. If an attempt is + made to attach uprobes to too many containers, it can have a negative + impact on on the cluster. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object + func_name: + description: Function to attach the uprobe to. + type: string + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + offset: + default: 0 + description: Offset added to the address of the function + for uprobe. + format: int64 + type: integer + pid: + description: |- + Only execute uprobe for given process identification number (PID). If PID + is not provided, uprobe executes for all PIDs. + format: int32 + type: integer + retprobe: + default: false + description: Whether the program is a uretprobe. Default + is false + type: boolean + target: + description: Library name or the absolute path to a binary + or library. + type: string + required: + - bpffunctionname + - containers + - target + type: object + uretprobe: + description: uretprobe defines the desired state of the application's + UretprobeNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the uprobe. + If Containers is not specified, the uprobe will be attached in the + bpfman-agent container. The ContainerNsSelector is very flexible and even + allows the selection of all containers in a cluster. If an attempt is + made to attach uprobes to too many containers, it can have a negative + impact on on the cluster. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object + func_name: + description: Function to attach the uprobe to. + type: string + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + offset: + default: 0 + description: Offset added to the address of the function + for uprobe. + format: int64 + type: integer + pid: + description: |- + Only execute uprobe for given process identification number (PID). If PID + is not provided, uprobe executes for all PIDs. + format: int32 + type: integer + retprobe: + default: false + description: Whether the program is a uretprobe. Default + is false + type: boolean + target: + description: Library name or the absolute path to a binary + or library. + type: string + required: + - bpffunctionname + - containers + - target + type: object + xdp: + description: xdp defines the desired state of the application's + XdpNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object + interfaceselector: + description: Selector to determine the network interface + (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface + on the node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + priority: + description: |- + Priority specifies the priority of the bpf program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + proceedon: + default: + - pass + - dispatcher_return + description: |- + ProceedOn allows the user to call other xdp programs in chain on this exit code. + Multiple values are supported by repeating the parameter. + items: + enum: + - aborted + - drop + - pass + - tx + - redirect + - dispatcher_return + type: string + maxItems: 6 + type: array + required: + - bpffunctionname + - containers + - interfaceselector + - priority + type: object + type: object + x-kubernetes-validations: + - message: xdp configuration is required when type is XDP, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''XDP'' ? has(self.xdp) + : !has(self.xdp)' + - message: tc configuration is required when type is TC, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''TC'' ? has(self.tc) : + !has(self.tc)' + - message: tcx configuration is required when type is TCX, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''TCX'' ? has(self.tcx) + : !has(self.tcx)' + - message: uprobe configuration is required when type is Uprobe, + and forbidden otherwise + rule: 'has(self.type) && self.type == ''Uprobe'' ? has(self.uprobe) + : !has(self.uprobe)' + - message: uretprobe configuration is required when type is Uretprobe, + and forbidden otherwise + rule: 'has(self.type) && self.type == ''Uretprobe'' ? has(self.uretprobe) + : !has(self.uretprobe)' + minItems: 1 + type: array + required: + - bytecode + - nodeselector + type: object + status: + description: BpfApplicationStatus defines the observed state of BpfApplication + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/bpfman.io_bpfnsprograms.yaml b/bundle/manifests/bpfman.io_bpfnsprograms.yaml new file mode 100644 index 000000000..606780fdd --- /dev/null +++ b/bundle/manifests/bpfman.io_bpfnsprograms.yaml @@ -0,0 +1,150 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + creationTimestamp: null + name: bpfnsprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: BpfNsProgram + listKind: BpfNsProgramList + plural: bpfnsprograms + singular: bpfnsprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.type + name: Type + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: BpfNsProgram is the Schema for the Bpfnsprograms API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BpfProgramSpec defines the desired state of BpfProgram + properties: + type: + description: Type specifies the bpf program type + type: string + type: object + status: + description: |- + BpfProgramStatus defines the observed state of BpfProgram + TODO Make these a fixed set of metav1.Condition.types and metav1.Condition.reasons + properties: + conditions: + description: |- + Conditions houses the updates regarding the actual implementation of + the bpf program on the node + Known .status.conditions.type are: "Available", "Progressing", and "Degraded" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/bpfman.io_tcnsprograms.yaml b/bundle/manifests/bpfman.io_tcnsprograms.yaml new file mode 100644 index 000000000..f2602002c --- /dev/null +++ b/bundle/manifests/bpfman.io_tcnsprograms.yaml @@ -0,0 +1,447 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + creationTimestamp: null + name: tcnsprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: TcNsProgram + listKind: TcNsProgramList + plural: tcnsprograms + singular: tcnsprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bpffunctionname + name: BpfFunctionName + type: string + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .spec.priority + name: Priority + priority: 1 + type: string + - jsonPath: .spec.direction + name: Direction + priority: 1 + type: string + - jsonPath: .spec.interfaceselector + name: InterfaceSelector + priority: 1 + type: string + - jsonPath: .spec.proceedon + name: ProceedOn + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: TcNsProgram is the Schema for the TcNsProgram API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: TcNsProgramSpec defines the desired state of TcNsProgram + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object + direction: + description: |- + Direction specifies the direction of traffic the tc program should + attach to for a given network device. + enum: + - ingress + - egress + type: string + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + interfaceselector: + description: Selector to determine the network interface (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface on the + node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + priority: + description: |- + Priority specifies the priority of the tc program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + proceedon: + default: + - pipe + - dispatcher_return + description: |- + ProceedOn allows the user to call other tc programs in chain on this exit code. + Multiple values are supported by repeating the parameter. + items: + enum: + - unspec + - ok + - reclassify + - shot + - pipe + - stolen + - queued + - repeat + - redirect + - trap + - dispatcher_return + type: string + maxItems: 11 + type: array + required: + - bpffunctionname + - bytecode + - containers + - direction + - interfaceselector + - nodeselector + - priority + type: object + status: + description: TcProgramStatus defines the observed state of TcProgram + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/bpfman.io_tcprograms.yaml b/bundle/manifests/bpfman.io_tcprograms.yaml index db4073a6c..6bd329110 100644 --- a/bundle/manifests/bpfman.io_tcprograms.yaml +++ b/bundle/manifests/bpfman.io_tcprograms.yaml @@ -117,7 +117,7 @@ spec: type: object containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: diff --git a/bundle/manifests/bpfman.io_tcxnsprograms.yaml b/bundle/manifests/bpfman.io_tcxnsprograms.yaml new file mode 100644 index 000000000..947e5c32d --- /dev/null +++ b/bundle/manifests/bpfman.io_tcxnsprograms.yaml @@ -0,0 +1,424 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + creationTimestamp: null + name: tcxnsprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: TcxNsProgram + listKind: TcxNsProgramList + plural: tcxnsprograms + singular: tcxnsprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bpffunctionname + name: BpfFunctionName + type: string + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .spec.direction + name: Direction + priority: 1 + type: string + - jsonPath: .spec.interfaceselector + name: InterfaceSelector + priority: 1 + type: string + - jsonPath: .spec.position + name: Position + priority: 1 + type: string + - jsonPath: .spec.priority + name: Priority + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: TcxNsProgram is the Schema for the TcxNsProgram API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: TcxNsProgramSpec defines the desired state of TcxNsProgram + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object + direction: + description: |- + Direction specifies the direction of traffic the tcx program should + attach to for a given network device. + enum: + - ingress + - egress + type: string + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + interfaceselector: + description: Selector to determine the network interface (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface on the + node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + priority: + description: |- + Priority specifies the priority of the tc program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + required: + - bpffunctionname + - bytecode + - containers + - direction + - interfaceselector + - nodeselector + - priority + type: object + status: + description: TcxProgramStatus defines the observed state of TcxProgram + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/bpfman.io_tcxprograms.yaml b/bundle/manifests/bpfman.io_tcxprograms.yaml index f176c2f80..00ee3d933 100644 --- a/bundle/manifests/bpfman.io_tcxprograms.yaml +++ b/bundle/manifests/bpfman.io_tcxprograms.yaml @@ -117,7 +117,7 @@ spec: type: object containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: @@ -335,7 +335,7 @@ spec: - priority type: object status: - description: TcxProgramStatus defines the observed state of TcProgram + description: TcxProgramStatus defines the observed state of TcxProgram properties: conditions: description: |- diff --git a/bundle/manifests/bpfman.io_uprobensprograms.yaml b/bundle/manifests/bpfman.io_uprobensprograms.yaml new file mode 100644 index 000000000..f16f7f81c --- /dev/null +++ b/bundle/manifests/bpfman.io_uprobensprograms.yaml @@ -0,0 +1,417 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + creationTimestamp: null + name: uprobensprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: UprobeNsProgram + listKind: UprobeNsProgramList + plural: uprobensprograms + singular: uprobensprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bpffunctionname + name: BpfFunctionName + type: string + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .spec.func_name + name: FunctionName + priority: 1 + type: string + - jsonPath: .spec.offset + name: Offset + priority: 1 + type: integer + - jsonPath: .spec.target + name: Target + priority: 1 + type: string + - jsonPath: .spec.retprobe + name: RetProbe + priority: 1 + type: boolean + - jsonPath: .spec.pid + name: Pid + priority: 1 + type: integer + name: v1alpha1 + schema: + openAPIV3Schema: + description: UprobeNsProgram is the Schema for the UprobeNsPrograms API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: UprobeNsProgramSpec defines the desired state of UprobeProgram + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + containers: + description: |- + Containers identifies the set of containers in which to attach the uprobe. + If Containers is not specified, the uprobe will be attached in the + bpfman-agent container. The ContainerNsSelector is very flexible and even + allows the selection of all containers in a cluster. If an attempt is + made to attach uprobes to too many containers, it can have a negative + impact on on the cluster. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object + func_name: + description: Function to attach the uprobe to. + type: string + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + offset: + default: 0 + description: Offset added to the address of the function for uprobe. + format: int64 + type: integer + pid: + description: |- + Only execute uprobe for given process identification number (PID). If PID + is not provided, uprobe executes for all PIDs. + format: int32 + type: integer + retprobe: + default: false + description: Whether the program is a uretprobe. Default is false + type: boolean + target: + description: Library name or the absolute path to a binary or library. + type: string + required: + - bpffunctionname + - bytecode + - containers + - nodeselector + - target + type: object + status: + description: UprobeProgramStatus defines the observed state of UprobeProgram + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/bpfman.io_uprobeprograms.yaml b/bundle/manifests/bpfman.io_uprobeprograms.yaml index 376ccbe27..a3cdab8ad 100644 --- a/bundle/manifests/bpfman.io_uprobeprograms.yaml +++ b/bundle/manifests/bpfman.io_uprobeprograms.yaml @@ -121,7 +121,7 @@ spec: type: object containers: description: |- - Containers identifes the set of containers in which to attach the uprobe. + Containers identifies the set of containers in which to attach the uprobe. If Containers is not specified, the uprobe will be attached in the bpfman-agent container. The ContainerSelector is very flexible and even allows the selection of all containers in a cluster. If an attempt is diff --git a/bundle/manifests/bpfman.io_xdpnsprograms.yaml b/bundle/manifests/bpfman.io_xdpnsprograms.yaml new file mode 100644 index 000000000..1b72eba49 --- /dev/null +++ b/bundle/manifests/bpfman.io_xdpnsprograms.yaml @@ -0,0 +1,429 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + creationTimestamp: null + name: xdpnsprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: XdpNsProgram + listKind: XdpNsProgramList + plural: xdpnsprograms + singular: xdpnsprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bpffunctionname + name: BpfFunctionName + type: string + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .spec.priority + name: Priority + priority: 1 + type: string + - jsonPath: .spec.interfaceselector + name: InterfaceSelector + priority: 1 + type: string + - jsonPath: .spec.proceedon + name: ProceedOn + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: XdpNsProgram is the Schema for the XdpNsPrograms API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: XdpNsProgramSpec defines the desired state of XdpNsProgram + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + interfaceselector: + description: Selector to determine the network interface (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface on the + node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + priority: + description: |- + Priority specifies the priority of the bpf program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + proceedon: + default: + - pass + - dispatcher_return + description: |- + ProceedOn allows the user to call other xdp programs in chain on this exit code. + Multiple values are supported by repeating the parameter. + items: + enum: + - aborted + - drop + - pass + - tx + - redirect + - dispatcher_return + type: string + maxItems: 6 + type: array + required: + - bpffunctionname + - bytecode + - containers + - interfaceselector + - nodeselector + - priority + type: object + status: + description: XdpProgramStatus defines the observed state of XdpProgram + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/bpfman.io_xdpprograms.yaml b/bundle/manifests/bpfman.io_xdpprograms.yaml index 31791544f..f55582862 100644 --- a/bundle/manifests/bpfman.io_xdpprograms.yaml +++ b/bundle/manifests/bpfman.io_xdpprograms.yaml @@ -113,7 +113,7 @@ spec: type: object containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: diff --git a/cmd/bpfman-agent/main.go b/cmd/bpfman-agent/main.go index 82da32bd3..daaca165c 100644 --- a/cmd/bpfman-agent/main.go +++ b/cmd/bpfman-agent/main.go @@ -145,7 +145,7 @@ func main() { os.Exit(1) } - common := bpfmanagent.ReconcilerCommon{ + common := bpfmanagent.ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), GrpcConn: conn, @@ -154,69 +154,120 @@ func main() { Containers: containerGetter, } - if err = (&bpfmanagent.XdpProgramReconciler{ + commonCluster := bpfmanagent.ClusterProgramReconciler{ ReconcilerCommon: common, + } + + commonNs := bpfmanagent.ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + GrpcConn: conn, + BpfmanClient: gobpfman.NewBpfmanClient(conn), + NodeName: nodeName, + Containers: containerGetter, + } + + commonNamespace := bpfmanagent.NamespaceProgramReconciler{ + ReconcilerCommon: commonNs, + } + + if err = (&bpfmanagent.XdpProgramReconciler{ + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create xdpProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanagent.TcProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create tcProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanagent.TcxProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create tcxProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanagent.TracepointProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create tracepointProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanagent.KprobeProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create kprobeProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanagent.UprobeProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create uprobeProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanagent.FentryProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create fentryProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanagent.FexitProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create fexitProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanagent.BpfApplicationReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create BpfApplicationProgram controller", "controller", "BpfProgram") os.Exit(1) } + if err = (&bpfmanagent.TcNsProgramReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create tcNsProgram controller", "controller", "BpfNsProgram") + os.Exit(1) + } + + if err = (&bpfmanagent.TcxNsProgramReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create tcxNsProgram controller", "controller", "BpfNsProgram") + os.Exit(1) + } + + if err = (&bpfmanagent.UprobeNsProgramReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create uprobeNsProgram controller", "controller", "BpfNsProgram") + os.Exit(1) + } + + if err = (&bpfmanagent.XdpNsProgramReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create xdpNsProgram controller", "controller", "BpfNsProgram") + os.Exit(1) + } + + if err = (&bpfmanagent.BpfNsApplicationReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create BpfNsApplicationProgram controller", "controller", "BpfNsProgram") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/cmd/bpfman-operator/main.go b/cmd/bpfman-operator/main.go index b6f2e86a2..1b933de6e 100644 --- a/cmd/bpfman-operator/main.go +++ b/cmd/bpfman-operator/main.go @@ -58,7 +58,7 @@ func init() { } // Returns true if the current platform is Openshift. -func isOpenshift(client discovery.DiscoveryInterface, cfg *rest.Config) (bool, error) { +func isOpenshift(client discovery.DiscoveryInterface, _ *rest.Config) (bool, error) { k8sVersion, err := client.ServerVersion() if err != nil { setupLog.Info("issue occurred while fetching ServerVersion") @@ -163,11 +163,24 @@ func main() { os.Exit(1) } - common := bpfmanoperator.ReconcilerCommon{ + common := bpfmanoperator.ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), } + commonCluster := bpfmanoperator.ClusterProgramReconciler{ + ReconcilerCommon: common, + } + + commonNs := bpfmanoperator.ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } + + commonNamespace := bpfmanoperator.NamespaceProgramReconciler{ + ReconcilerCommon: commonNs, + } + setupLog.Info("Discovering APIs") dc, err := discovery.NewDiscoveryClientForConfig(mgr.GetConfig()) if err != nil { @@ -183,7 +196,7 @@ func main() { } if err = (&bpfmanoperator.BpfmanConfigReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, BpfmanStandardDeployment: internal.BpfmanDaemonManifestPath, CsiDriverDeployment: internal.BpfmanCsiDriverPath, RestrictedSCC: internal.BpfmanRestrictedSCCPath, @@ -194,67 +207,102 @@ func main() { } if err = (&bpfmanoperator.XdpProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create xdpProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanoperator.TcProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create tcProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanoperator.TcxProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create tcxProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanoperator.TracepointProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create tracepointProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanoperator.KprobeProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create kprobeProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanoperator.UprobeProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create uprobeProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanoperator.FentryProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create fentryProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanoperator.FexitProgramReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create fexitProgram controller", "controller", "BpfProgram") os.Exit(1) } if err = (&bpfmanoperator.BpfApplicationReconciler{ - ReconcilerCommon: common, + ClusterProgramReconciler: commonCluster, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "BpfApplication") os.Exit(1) } + + if err = (&bpfmanoperator.TcNsProgramReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create tcProgram controller", "controller", "BpfNsProgram") + os.Exit(1) + } + + if err = (&bpfmanoperator.TcxNsProgramReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create tcxNsProgram controller", "controller", "BpfNsProgram") + os.Exit(1) + } + + if err = (&bpfmanoperator.UprobeNsProgramReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create uprobeNsProgram controller", "controller", "BpfNsProgram") + os.Exit(1) + } + + if err = (&bpfmanoperator.XdpNsProgramReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create xdpNsProgram controller", "controller", "BpfNsProgram") + os.Exit(1) + } + + if err = (&bpfmanoperator.BpfNsApplicationReconciler{ + NamespaceProgramReconciler: commonNamespace, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "BpfNsApplication") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/config/crd/bases/bpfman.io_bpfapplications.yaml b/config/crd/bases/bpfman.io_bpfapplications.yaml index cc775ec1a..da9ec0e06 100644 --- a/config/crd/bases/bpfman.io_bpfapplications.yaml +++ b/config/crd/bases/bpfman.io_bpfapplications.yaml @@ -461,7 +461,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: @@ -652,7 +652,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: @@ -904,7 +904,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the uprobe. + Containers identifies the set of containers in which to attach the uprobe. If Containers is not specified, the uprobe will be attached in the bpfman-agent container. The ContainerSelector is very flexible and even allows the selection of all containers in a cluster. If an attempt is @@ -1062,7 +1062,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the uprobe. + Containers identifies the set of containers in which to attach the uprobe. If Containers is not specified, the uprobe will be attached in the bpfman-agent container. The ContainerSelector is very flexible and even allows the selection of all containers in a cluster. If an attempt is @@ -1220,7 +1220,7 @@ spec: type: string containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: diff --git a/config/crd/bases/bpfman.io_bpfnsapplications.yaml b/config/crd/bases/bpfman.io_bpfnsapplications.yaml new file mode 100644 index 000000000..403eaf2e2 --- /dev/null +++ b/config/crd/bases/bpfman.io_bpfnsapplications.yaml @@ -0,0 +1,1119 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: bpfnsapplications.bpfman.io +spec: + group: bpfman.io + names: + kind: BpfNsApplication + listKind: BpfNsApplicationList + plural: bpfnsapplications + singular: bpfnsapplication + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: BpfNsApplication is the Schema for the bpfapplications API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BpfApplicationSpec defines the desired state of BpfApplication + properties: + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + programs: + description: |- + Programs is a list of bpf programs supported for a specific application. + It's possible that the application can selectively choose which program(s) + to run from this list. + items: + description: BpfNsApplicationProgram defines the desired state of + BpfApplication + properties: + tc: + description: tc defines the desired state of the application's + TcNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object + direction: + description: |- + Direction specifies the direction of traffic the tc program should + attach to for a given network device. + enum: + - ingress + - egress + type: string + interfaceselector: + description: Selector to determine the network interface + (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface + on the node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + priority: + description: |- + Priority specifies the priority of the tc program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + proceedon: + default: + - pipe + - dispatcher_return + description: |- + ProceedOn allows the user to call other tc programs in chain on this exit code. + Multiple values are supported by repeating the parameter. + items: + enum: + - unspec + - ok + - reclassify + - shot + - pipe + - stolen + - queued + - repeat + - redirect + - trap + - dispatcher_return + type: string + maxItems: 11 + type: array + required: + - bpffunctionname + - containers + - direction + - interfaceselector + - priority + type: object + tcx: + description: tcx defines the desired state of the application's + TcxNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object + direction: + description: |- + Direction specifies the direction of traffic the tcx program should + attach to for a given network device. + enum: + - ingress + - egress + type: string + interfaceselector: + description: Selector to determine the network interface + (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface + on the node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + priority: + description: |- + Priority specifies the priority of the tc program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + required: + - bpffunctionname + - containers + - direction + - interfaceselector + - priority + type: object + type: + description: Type specifies the bpf program type + enum: + - XDP + - TC + - TCX + - Uprobe + - Uretprobe + type: string + uprobe: + description: uprobe defines the desired state of the application's + UprobeNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the uprobe. + If Containers is not specified, the uprobe will be attached in the + bpfman-agent container. The ContainerNsSelector is very flexible and even + allows the selection of all containers in a cluster. If an attempt is + made to attach uprobes to too many containers, it can have a negative + impact on on the cluster. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object + func_name: + description: Function to attach the uprobe to. + type: string + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + offset: + default: 0 + description: Offset added to the address of the function + for uprobe. + format: int64 + type: integer + pid: + description: |- + Only execute uprobe for given process identification number (PID). If PID + is not provided, uprobe executes for all PIDs. + format: int32 + type: integer + retprobe: + default: false + description: Whether the program is a uretprobe. Default + is false + type: boolean + target: + description: Library name or the absolute path to a binary + or library. + type: string + required: + - bpffunctionname + - containers + - target + type: object + uretprobe: + description: uretprobe defines the desired state of the application's + UretprobeNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the uprobe. + If Containers is not specified, the uprobe will be attached in the + bpfman-agent container. The ContainerNsSelector is very flexible and even + allows the selection of all containers in a cluster. If an attempt is + made to attach uprobes to too many containers, it can have a negative + impact on on the cluster. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object + func_name: + description: Function to attach the uprobe to. + type: string + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + offset: + default: 0 + description: Offset added to the address of the function + for uprobe. + format: int64 + type: integer + pid: + description: |- + Only execute uprobe for given process identification number (PID). If PID + is not provided, uprobe executes for all PIDs. + format: int32 + type: integer + retprobe: + default: false + description: Whether the program is a uretprobe. Default + is false + type: boolean + target: + description: Library name or the absolute path to a binary + or library. + type: string + required: + - bpffunctionname + - containers + - target + type: object + xdp: + description: xdp defines the desired state of the application's + XdpNsPrograms. + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object + interfaceselector: + description: Selector to determine the network interface + (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface + on the node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + priority: + description: |- + Priority specifies the priority of the bpf program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + proceedon: + default: + - pass + - dispatcher_return + description: |- + ProceedOn allows the user to call other xdp programs in chain on this exit code. + Multiple values are supported by repeating the parameter. + items: + enum: + - aborted + - drop + - pass + - tx + - redirect + - dispatcher_return + type: string + maxItems: 6 + type: array + required: + - bpffunctionname + - containers + - interfaceselector + - priority + type: object + type: object + x-kubernetes-validations: + - message: xdp configuration is required when type is XDP, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''XDP'' ? has(self.xdp) + : !has(self.xdp)' + - message: tc configuration is required when type is TC, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''TC'' ? has(self.tc) : + !has(self.tc)' + - message: tcx configuration is required when type is TCX, and forbidden + otherwise + rule: 'has(self.type) && self.type == ''TCX'' ? has(self.tcx) + : !has(self.tcx)' + - message: uprobe configuration is required when type is Uprobe, + and forbidden otherwise + rule: 'has(self.type) && self.type == ''Uprobe'' ? has(self.uprobe) + : !has(self.uprobe)' + - message: uretprobe configuration is required when type is Uretprobe, + and forbidden otherwise + rule: 'has(self.type) && self.type == ''Uretprobe'' ? has(self.uretprobe) + : !has(self.uretprobe)' + minItems: 1 + type: array + required: + - bytecode + - nodeselector + type: object + status: + description: BpfApplicationStatus defines the observed state of BpfApplication + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/bpfman.io_bpfnsprograms.yaml b/config/crd/bases/bpfman.io_bpfnsprograms.yaml new file mode 100644 index 000000000..3cd7faabb --- /dev/null +++ b/config/crd/bases/bpfman.io_bpfnsprograms.yaml @@ -0,0 +1,144 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: bpfnsprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: BpfNsProgram + listKind: BpfNsProgramList + plural: bpfnsprograms + singular: bpfnsprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.type + name: Type + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: BpfNsProgram is the Schema for the Bpfnsprograms API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BpfProgramSpec defines the desired state of BpfProgram + properties: + type: + description: Type specifies the bpf program type + type: string + type: object + status: + description: |- + BpfProgramStatus defines the observed state of BpfProgram + TODO Make these a fixed set of metav1.Condition.types and metav1.Condition.reasons + properties: + conditions: + description: |- + Conditions houses the updates regarding the actual implementation of + the bpf program on the node + Known .status.conditions.type are: "Available", "Progressing", and "Degraded" + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/bpfman.io_tcnsprograms.yaml b/config/crd/bases/bpfman.io_tcnsprograms.yaml new file mode 100644 index 000000000..12a633c45 --- /dev/null +++ b/config/crd/bases/bpfman.io_tcnsprograms.yaml @@ -0,0 +1,441 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: tcnsprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: TcNsProgram + listKind: TcNsProgramList + plural: tcnsprograms + singular: tcnsprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bpffunctionname + name: BpfFunctionName + type: string + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .spec.priority + name: Priority + priority: 1 + type: string + - jsonPath: .spec.direction + name: Direction + priority: 1 + type: string + - jsonPath: .spec.interfaceselector + name: InterfaceSelector + priority: 1 + type: string + - jsonPath: .spec.proceedon + name: ProceedOn + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: TcNsProgram is the Schema for the TcNsProgram API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: TcNsProgramSpec defines the desired state of TcNsProgram + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object + direction: + description: |- + Direction specifies the direction of traffic the tc program should + attach to for a given network device. + enum: + - ingress + - egress + type: string + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + interfaceselector: + description: Selector to determine the network interface (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface on the + node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + priority: + description: |- + Priority specifies the priority of the tc program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + proceedon: + default: + - pipe + - dispatcher_return + description: |- + ProceedOn allows the user to call other tc programs in chain on this exit code. + Multiple values are supported by repeating the parameter. + items: + enum: + - unspec + - ok + - reclassify + - shot + - pipe + - stolen + - queued + - repeat + - redirect + - trap + - dispatcher_return + type: string + maxItems: 11 + type: array + required: + - bpffunctionname + - bytecode + - containers + - direction + - interfaceselector + - nodeselector + - priority + type: object + status: + description: TcProgramStatus defines the observed state of TcProgram + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/bpfman.io_tcprograms.yaml b/config/crd/bases/bpfman.io_tcprograms.yaml index 713629be2..fa56278c8 100644 --- a/config/crd/bases/bpfman.io_tcprograms.yaml +++ b/config/crd/bases/bpfman.io_tcprograms.yaml @@ -117,7 +117,7 @@ spec: type: object containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: diff --git a/config/crd/bases/bpfman.io_tcxnsprograms.yaml b/config/crd/bases/bpfman.io_tcxnsprograms.yaml new file mode 100644 index 000000000..f7508f18e --- /dev/null +++ b/config/crd/bases/bpfman.io_tcxnsprograms.yaml @@ -0,0 +1,418 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: tcxnsprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: TcxNsProgram + listKind: TcxNsProgramList + plural: tcxnsprograms + singular: tcxnsprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bpffunctionname + name: BpfFunctionName + type: string + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .spec.direction + name: Direction + priority: 1 + type: string + - jsonPath: .spec.interfaceselector + name: InterfaceSelector + priority: 1 + type: string + - jsonPath: .spec.position + name: Position + priority: 1 + type: string + - jsonPath: .spec.priority + name: Priority + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: TcxNsProgram is the Schema for the TcxNsProgram API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: TcxNsProgramSpec defines the desired state of TcxNsProgram + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object + direction: + description: |- + Direction specifies the direction of traffic the tcx program should + attach to for a given network device. + enum: + - ingress + - egress + type: string + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + interfaceselector: + description: Selector to determine the network interface (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface on the + node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + priority: + description: |- + Priority specifies the priority of the tc program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + required: + - bpffunctionname + - bytecode + - containers + - direction + - interfaceselector + - nodeselector + - priority + type: object + status: + description: TcxProgramStatus defines the observed state of TcxProgram + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/bpfman.io_tcxprograms.yaml b/config/crd/bases/bpfman.io_tcxprograms.yaml index d0eca6137..bf97f8e41 100644 --- a/config/crd/bases/bpfman.io_tcxprograms.yaml +++ b/config/crd/bases/bpfman.io_tcxprograms.yaml @@ -117,7 +117,7 @@ spec: type: object containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: @@ -335,7 +335,7 @@ spec: - priority type: object status: - description: TcxProgramStatus defines the observed state of TcProgram + description: TcxProgramStatus defines the observed state of TcxProgram properties: conditions: description: |- diff --git a/config/crd/bases/bpfman.io_uprobensprograms.yaml b/config/crd/bases/bpfman.io_uprobensprograms.yaml new file mode 100644 index 000000000..45be66c7d --- /dev/null +++ b/config/crd/bases/bpfman.io_uprobensprograms.yaml @@ -0,0 +1,411 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: uprobensprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: UprobeNsProgram + listKind: UprobeNsProgramList + plural: uprobensprograms + singular: uprobensprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bpffunctionname + name: BpfFunctionName + type: string + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .spec.func_name + name: FunctionName + priority: 1 + type: string + - jsonPath: .spec.offset + name: Offset + priority: 1 + type: integer + - jsonPath: .spec.target + name: Target + priority: 1 + type: string + - jsonPath: .spec.retprobe + name: RetProbe + priority: 1 + type: boolean + - jsonPath: .spec.pid + name: Pid + priority: 1 + type: integer + name: v1alpha1 + schema: + openAPIV3Schema: + description: UprobeNsProgram is the Schema for the UprobeNsPrograms API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: UprobeNsProgramSpec defines the desired state of UprobeProgram + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + containers: + description: |- + Containers identifies the set of containers in which to attach the uprobe. + If Containers is not specified, the uprobe will be attached in the + bpfman-agent container. The ContainerNsSelector is very flexible and even + allows the selection of all containers in a cluster. If an attempt is + made to attach uprobes to too many containers, it can have a negative + impact on on the cluster. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object + func_name: + description: Function to attach the uprobe to. + type: string + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + offset: + default: 0 + description: Offset added to the address of the function for uprobe. + format: int64 + type: integer + pid: + description: |- + Only execute uprobe for given process identification number (PID). If PID + is not provided, uprobe executes for all PIDs. + format: int32 + type: integer + retprobe: + default: false + description: Whether the program is a uretprobe. Default is false + type: boolean + target: + description: Library name or the absolute path to a binary or library. + type: string + required: + - bpffunctionname + - bytecode + - containers + - nodeselector + - target + type: object + status: + description: UprobeProgramStatus defines the observed state of UprobeProgram + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/bpfman.io_uprobeprograms.yaml b/config/crd/bases/bpfman.io_uprobeprograms.yaml index 356c58008..26e3daf9b 100644 --- a/config/crd/bases/bpfman.io_uprobeprograms.yaml +++ b/config/crd/bases/bpfman.io_uprobeprograms.yaml @@ -121,7 +121,7 @@ spec: type: object containers: description: |- - Containers identifes the set of containers in which to attach the uprobe. + Containers identifies the set of containers in which to attach the uprobe. If Containers is not specified, the uprobe will be attached in the bpfman-agent container. The ContainerSelector is very flexible and even allows the selection of all containers in a cluster. If an attempt is diff --git a/config/crd/bases/bpfman.io_xdpnsprograms.yaml b/config/crd/bases/bpfman.io_xdpnsprograms.yaml new file mode 100644 index 000000000..971eb5949 --- /dev/null +++ b/config/crd/bases/bpfman.io_xdpnsprograms.yaml @@ -0,0 +1,423 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: xdpnsprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: XdpNsProgram + listKind: XdpNsProgramList + plural: xdpnsprograms + singular: xdpnsprogram + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bpffunctionname + name: BpfFunctionName + type: string + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .spec.priority + name: Priority + priority: 1 + type: string + - jsonPath: .spec.interfaceselector + name: InterfaceSelector + priority: 1 + type: string + - jsonPath: .spec.proceedon + name: ProceedOn + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: XdpNsProgram is the Schema for the XdpNsPrograms API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: XdpNsProgramSpec defines the desired state of XdpNsProgram + properties: + bpffunctionname: + description: |- + BpfFunctionName is the name of the function that is the entry point for the BPF + program + type: string + bytecode: + description: |- + Bytecode configures where the bpf program's bytecode should be loaded + from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: |- + ImagePullSecret is the name of the secret bpfman should use to get remote image + repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + containers: + description: |- + Containers identifies the set of containers in which to attach the eBPF + program. + properties: + containernames: + description: |- + Name(s) of container(s). If none are specified, all containers in the + pod are selected. + items: + type: string + type: array + pods: + description: |- + Target pods. This field must be specified, to select all pods use + standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object + globaldata: + additionalProperties: + format: byte + type: string + description: |- + GlobalData allows the user to set global variables when the program is loaded + with an array of raw bytes. This is a very low level primitive. The caller + is responsible for formatting the byte string appropriately considering + such things as size, endianness, alignment and packing of data structures. + type: object + interfaceselector: + description: Selector to determine the network interface (or interfaces) + maxProperties: 1 + minProperties: 1 + properties: + interfaces: + description: |- + Interfaces refers to a list of network interfaces to attach the BPF + program to. + items: + type: string + type: array + primarynodeinterface: + description: Attach BPF program to the primary interface on the + node. Only 'true' accepted. + type: boolean + type: object + mapownerselector: + description: |- + MapOwnerSelector is used to select the loaded eBPF program this eBPF program + will share a map with. The value is a label applied to the BpfProgram to select. + The selector must resolve to exactly one instance of a BpfProgram on a given node + or the eBPF program will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + nodeselector: + description: |- + NodeSelector allows the user to specify which nodes to deploy the + bpf program to. This field must be specified, to select all nodes + use standard metav1.LabelSelector semantics and make it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + priority: + description: |- + Priority specifies the priority of the bpf program in relation to + other programs of the same type with the same attach point. It is a value + from 0 to 1000 where lower values have higher precedence. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + proceedon: + default: + - pass + - dispatcher_return + description: |- + ProceedOn allows the user to call other xdp programs in chain on this exit code. + Multiple values are supported by repeating the parameter. + items: + enum: + - aborted + - drop + - pass + - tx + - redirect + - dispatcher_return + type: string + maxItems: 6 + type: array + required: + - bpffunctionname + - bytecode + - containers + - interfaceselector + - nodeselector + - priority + type: object + status: + description: XdpProgramStatus defines the observed state of XdpProgram + properties: + conditions: + description: |- + Conditions houses the global cluster state for the eBPFProgram. The explicit + condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/bpfman.io_xdpprograms.yaml b/config/crd/bases/bpfman.io_xdpprograms.yaml index 3e15190b0..38b1cafb6 100644 --- a/config/crd/bases/bpfman.io_xdpprograms.yaml +++ b/config/crd/bases/bpfman.io_xdpprograms.yaml @@ -113,7 +113,7 @@ spec: type: object containers: description: |- - Containers identifes the set of containers in which to attach the eBPF + Containers identifies the set of containers in which to attach the eBPF program. If Containers is not specified, the BPF program will be attached in the root network namespace. properties: diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 41da2bb50..2b77f35da 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -12,6 +12,12 @@ resources: - bases/bpfman.io_fentryprograms.yaml - bases/bpfman.io_fexitprograms.yaml - bases/bpfman.io_bpfapplications.yaml + - bases/bpfman.io_bpfnsprograms.yaml + - bases/bpfman.io_tcnsprograms.yaml + - bases/bpfman.io_tcxnsprograms.yaml + - bases/bpfman.io_xdpnsprograms.yaml + - bases/bpfman.io_uprobensprograms.yaml + - bases/bpfman.io_bpfnsapplications.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -19,6 +25,7 @@ patchesStrategicMerge: # patches here are for enabling the conversion webhook for each CRD #- patches/webhook_in_bpfprograms.yaml #- patches/webhook_in_tcprograms.yaml +#- patches/webhook_in_tcxprograms.yaml #- patches/webhook_in_xdpprograms.yaml #- patches/webhook_in_tracepointprograms.yaml #- patches/webhook_in_kprobeprograms.yaml @@ -26,12 +33,19 @@ patchesStrategicMerge: #- patches/webhook_in_fentryprograms.yaml #- patches/webhook_in_fexitprograms.yaml #- patches/webhook_in_bpfapplications.yaml +#- patches/webhook_in_bpfnsprograms.yaml +#- patches/webhook_in_tcnsprograms.yaml +#- patches/webhook_in_tcxnsprograms.yaml +#- patches/webhook_in_xdpnsprograms.yaml +#- patches/webhook_in_uprobensprograms.yaml +#- patches/webhook_in_bpfnsapplications.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. # patches here are for enabling the CA injection for each CRD #- patches/cainjection_in_bpfprograms.yaml #- patches/cainjection_in_tcprograms.yaml +#- patches/cainjection_in_tcxprograms.yaml #- patches/cainjection_in_xdpprograms.yaml #- patches/cainjection_in_tracepointprograms.yaml #- patches/cainjection_in_kprobeprograms.yaml @@ -39,6 +53,12 @@ patchesStrategicMerge: #- patches/cainjection_in_fentryprograms.yaml #- patches/cainjection_in_fexitrograms.yaml #- patches/cainjection_in_bpfapplications.yaml +#- patches/cainjection_in_bpfnsprograms.yaml +#- patches/cainjection_in_tcnsprograms.yaml +#- patches/cainjection_in_tcxnsprograms.yaml +#- patches/cainjection_in_xdpnsprograms.yaml +#- patches/cainjection_in_uprobensprograms.yaml +#- patches/cainjection_in_bpfnsapplications.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_bpfnsapplications.yaml b/config/crd/patches/cainjection_in_bpfnsapplications.yaml new file mode 100644 index 000000000..148537d83 --- /dev/null +++ b/config/crd/patches/cainjection_in_bpfnsapplications.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: bpfnsapplications.bpfman.io diff --git a/config/crd/patches/cainjection_in_bpfnsprograms.yaml b/config/crd/patches/cainjection_in_bpfnsprograms.yaml new file mode 100644 index 000000000..dddcc8ab4 --- /dev/null +++ b/config/crd/patches/cainjection_in_bpfnsprograms.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: bpfnsprograms.bpfman.io diff --git a/config/crd/patches/cainjection_in_tcnsprograms.yaml b/config/crd/patches/cainjection_in_tcnsprograms.yaml new file mode 100644 index 000000000..3fef5f5bf --- /dev/null +++ b/config/crd/patches/cainjection_in_tcnsprograms.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: tcnsprograms.bpfman.io diff --git a/config/crd/patches/cainjection_in_tcxnsprograms.yaml b/config/crd/patches/cainjection_in_tcxnsprograms.yaml new file mode 100644 index 000000000..95d7e86fc --- /dev/null +++ b/config/crd/patches/cainjection_in_tcxnsprograms.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: tcxnsprograms.bpfman.io diff --git a/config/crd/patches/cainjection_in_tcxprograms.yaml b/config/crd/patches/cainjection_in_tcxprograms.yaml new file mode 100644 index 000000000..e6e38a06f --- /dev/null +++ b/config/crd/patches/cainjection_in_tcxprograms.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: tcxprograms.bpfman.io diff --git a/config/crd/patches/cainjection_in_uprobensprograms.yaml b/config/crd/patches/cainjection_in_uprobensprograms.yaml new file mode 100644 index 000000000..4db3df4ed --- /dev/null +++ b/config/crd/patches/cainjection_in_uprobensprograms.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: uprobensprograms.bpfman.io diff --git a/config/crd/patches/cainjection_in_xdpnsprograms.yaml b/config/crd/patches/cainjection_in_xdpnsprograms.yaml new file mode 100644 index 000000000..9a44e43d8 --- /dev/null +++ b/config/crd/patches/cainjection_in_xdpnsprograms.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: xdpnsprograms.bpfman.io diff --git a/config/crd/patches/webhook_in_bpfnsapplications.yaml b/config/crd/patches/webhook_in_bpfnsapplications.yaml new file mode 100644 index 000000000..5329072fc --- /dev/null +++ b/config/crd/patches/webhook_in_bpfnsapplications.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: bpfnsapplications.bpfman.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_bpfnsprograms.yaml b/config/crd/patches/webhook_in_bpfnsprograms.yaml new file mode 100644 index 000000000..264ad53a6 --- /dev/null +++ b/config/crd/patches/webhook_in_bpfnsprograms.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: bpfnsprograms.bpfman.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_tcnsprograms.yaml b/config/crd/patches/webhook_in_tcnsprograms.yaml new file mode 100644 index 000000000..47cc1ef09 --- /dev/null +++ b/config/crd/patches/webhook_in_tcnsprograms.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: tcnsprograms.bpfman.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_tcxnsprograms.yaml b/config/crd/patches/webhook_in_tcxnsprograms.yaml new file mode 100644 index 000000000..b71fb1760 --- /dev/null +++ b/config/crd/patches/webhook_in_tcxnsprograms.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: tcxnsprograms.bpfman.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_tcxprograms.yaml b/config/crd/patches/webhook_in_tcxprograms.yaml new file mode 100644 index 000000000..f85eeee16 --- /dev/null +++ b/config/crd/patches/webhook_in_tcxprograms.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: tcxprograms.bpfman.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_uprobensprograms.yaml b/config/crd/patches/webhook_in_uprobensprograms.yaml new file mode 100644 index 000000000..9c6e1c68c --- /dev/null +++ b/config/crd/patches/webhook_in_uprobensprograms.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: uprobensprograms.bpfman.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_xdpnsprograms.yaml b/config/crd/patches/webhook_in_xdpnsprograms.yaml new file mode 100644 index 000000000..c4d2aba26 --- /dev/null +++ b/config/crd/patches/webhook_in_xdpnsprograms.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: xdpnsprograms.bpfman.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/manifests/bases/bpfman-operator.clusterserviceversion.yaml b/config/manifests/bases/bpfman-operator.clusterserviceversion.yaml index 255546b5c..84f4de3fd 100644 --- a/config/manifests/bases/bpfman-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/bpfman-operator.clusterserviceversion.yaml @@ -52,21 +52,51 @@ spec: kind: BpfApplication name: bpfapplications.bpfman.io version: v1alpha1 + - description: BpfNsApplication is the Schema for the BpfNsApplications API + displayName: Bpf Namespaced Application + kind: BpfNsApplication + name: bpfnsapplications.bpfman.io + version: v1alpha1 - description: BpfProgram is the Schema for the BpfProgram API displayName: Bpf Program kind: BpfProgram name: bpfprograms.bpfman.io version: v1alpha1 + - description: BpfNsProgram is the Schema for the BpfNsProgram API + displayName: Bpf Namespaced Program + kind: BpfNsProgram + name: bpfnsprograms.bpfman.io + version: v1alpha1 - description: XdpProgram is the Schema for the Xdpprograms API displayName: Xdp Program kind: XdpProgram name: xdpprograms.bpfman.io version: v1alpha1 + - description: XdpNsProgram is the Schema for the Xdpnsprograms API + displayName: Xdp Namespaced Program + kind: XdpNsProgram + name: xdpnsprograms.bpfman.io + version: v1alpha1 - description: TcProgram is the Schema for the Tcprograms API - displayName: Tc Program + displayName: TC Program kind: TcProgram name: tcprograms.bpfman.io version: v1alpha1 + - description: TcNsProgram is the Schema for the Tcnsprograms API + displayName: TC Namespaced Program + kind: TcNsProgram + name: tcnsprograms.bpfman.io + version: v1alpha1 + - description: TcxProgram is the Schema for the Tcxprograms API + displayName: TCX Program + kind: TcxProgram + name: tcxprograms.bpfman.io + version: v1alpha1 + - description: TcxNsProgram is the Schema for the Tcxnsprograms API + displayName: TCX Namespaced Program + kind: TcxNsProgram + name: tcxnsprograms.bpfman.io + version: v1alpha1 - description: TracepointProgram is the Schema for the Tracepointprograms API displayName: Tracepoint Program kind: TracepointProgram @@ -82,6 +112,11 @@ spec: kind: UprobeProgram name: uprobeprograms.bpfman.io version: v1alpha1 + - description: UprobeNsProgram is the Schema for the Uprobensprograms API + displayName: Uprobe Namespaced Program + kind: UprobeNsProgram + name: uprobensprograms.bpfman.io + version: v1alpha1 - description: FentryProgram is the Schema for the Fentryprograms API displayName: Fentry Program kind: FentryProgram @@ -106,8 +141,9 @@ spec: supports `debug`, `info`, `warn`, `error`, and `fatal`, defaults to `info`\n- `bpfman.agent.log.level`: the log level for the bpfman-agent currently supports `info`, `debug`, and `trace` \n\nThe bpfman operator deploys eBPF programs via CRDs. - The following CRDs are currently available, \n\n- BpfApplication\n- XdpProgram\n- TcProgram\n - - TracepointProgram\n- KprobeProgram\n- UprobeProgram\n- FentryProgram\n- FexitProgram\n\n + The following CRDs are currently available, \n\n- BpfApplication\n- FentryProgram\n- FexitProgram\n + - KprobeProgram\n- TcProgram\n- TcxProgram\n- TracepointProgram\n- UprobeProgram\n- XdpProgram\n + - BpfNsApplication\n- TcProgram\n- TcxNsProgram\n- UprobeNsProgram\n- XdpNsProgram\n\n ## More information\n\nPlease checkout the [bpfman community website](https://bpfman.io/) for more information." displayName: Bpfman Operator diff --git a/config/rbac/bpfman-agent/role.yaml b/config/rbac/bpfman-agent/role.yaml index da51037c7..a54843bc4 100644 --- a/config/rbac/bpfman-agent/role.yaml +++ b/config/rbac/bpfman-agent/role.yaml @@ -18,6 +18,40 @@ rules: - bpfapplications/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -86,6 +120,14 @@ rules: - kprobeprograms/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - tcnsprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: @@ -100,6 +142,14 @@ rules: - tcprograms/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: @@ -128,6 +178,14 @@ rules: - tracepointprograms/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - uprobensprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: @@ -142,6 +200,14 @@ rules: - uprobeprograms/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: @@ -178,3 +244,56 @@ rules: - secrets verbs: - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: agent-role + namespace: bpfman +rules: +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - tcnsprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - uprobensprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms/finalizers + verbs: + - update diff --git a/config/rbac/bpfman-operator/role.yaml b/config/rbac/bpfman-operator/role.yaml index 5e0913a4c..91bc8b962 100644 --- a/config/rbac/bpfman-operator/role.yaml +++ b/config/rbac/bpfman-operator/role.yaml @@ -42,6 +42,40 @@ rules: - get - patch - update +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications/status + verbs: + - get + - patch + - update +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - get + - list + - watch - apiGroups: - bpfman.io resources: @@ -134,6 +168,32 @@ rules: - get - patch - update +- apiGroups: + - bpfman.io + resources: + - tcnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - tcnsprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - tcnsprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -160,6 +220,32 @@ rules: - get - patch - update +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -212,6 +298,32 @@ rules: - get - patch - update +- apiGroups: + - bpfman.io + resources: + - uprobensprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - uprobensprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - uprobensprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -238,6 +350,32 @@ rules: - get - patch - update +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: @@ -301,3 +439,148 @@ rules: - get - list - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: operator-role + namespace: bpfman +rules: +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications/status + verbs: + - get + - patch + - update +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - tcnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - tcnsprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - tcnsprograms/status + verbs: + - get + - patch + - update +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - tcxnsprograms/status + verbs: + - get + - patch + - update +- apiGroups: + - bpfman.io + resources: + - uprobensprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - uprobensprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - uprobensprograms/status + verbs: + - get + - patch + - update +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - xdpnsprograms/status + verbs: + - get + - patch + - update diff --git a/config/rbac/bpfnsapplication_editor_role.yaml b/config/rbac/bpfnsapplication_editor_role.yaml new file mode 100644 index 000000000..cee4124aa --- /dev/null +++ b/config/rbac/bpfnsapplication_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit bpfapplications. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/name: role + app.kubernetes.io/instance: bpfnsapplication-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: bpfman-operator + app.kubernetes.io/part-of: bpfman-operator + app.kubernetes.io/managed-by: kustomize + name: bpfnsapplication-editor-role +rules: +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications/status + verbs: + - get diff --git a/config/rbac/bpfnsapplication_viewer_role.yaml b/config/rbac/bpfnsapplication_viewer_role.yaml new file mode 100644 index 000000000..4ae59969f --- /dev/null +++ b/config/rbac/bpfnsapplication_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view bpfapplications. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/name: role + app.kubernetes.io/instance: bpfnsapplication-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: bpfman-operator + app.kubernetes.io/part-of: bpfman-operator + app.kubernetes.io/managed-by: kustomize + name: bpfnsapplication-viewer-role +rules: +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsapplications/status + verbs: + - get diff --git a/config/rbac/bpfnsprogram_editor_role.yaml b/config/rbac/bpfnsprogram_editor_role.yaml new file mode 100644 index 000000000..973a04b0d --- /dev/null +++ b/config/rbac/bpfnsprogram_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit bpfprograms. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/name: role + app.kubernetes.io/instance: bpfnsprogram-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: bpfman-operator + app.kubernetes.io/part-of: bpfman-operator + app.kubernetes.io/managed-by: kustomize + name: bpfnsprogram-editor-role +rules: + - apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - bpfman.io + resources: + - bpfnsprograms/status + verbs: + - get diff --git a/config/rbac/bpfnsprogram_viewer_role.yaml b/config/rbac/bpfnsprogram_viewer_role.yaml new file mode 100644 index 000000000..1cd2ba154 --- /dev/null +++ b/config/rbac/bpfnsprogram_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view bpfprograms. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/name: role + app.kubernetes.io/instance: bpfnsprogram-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: bpfman-operator + app.kubernetes.io/part-of: bpfman-operator + app.kubernetes.io/managed-by: kustomize + name: bpfnsprogram-viewer-role +rules: + - apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - get + - list + - watch + - apiGroups: + - bpfman.io + resources: + - bpfnsprograms/status + verbs: + - get diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index ad78ce73a..2d6028e1d 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -12,6 +12,8 @@ resources: - leader_election_role_binding.yaml - bpfprogram_editor_role.yaml - bpfprogram_viewer_role.yaml + - bpfnsprogram_editor_role.yaml + - bpfnsprogram_viewer_role.yaml # Comment the following 4 lines if you want to disable # the auth proxy (https://github.com/brancz/kube-rbac-proxy) # which protects your /metrics endpoint. diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml index a1791f6f9..874a081e9 100644 --- a/config/rbac/role_binding.yaml +++ b/config/rbac/role_binding.yaml @@ -38,6 +38,26 @@ subjects: --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding +metadata: + labels: + app.kubernetes.io/name: rolebinding + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: bpfman-operator + app.kubernetes.io/part-of: bpfman-operator + app.kubernetes.io/managed-by: kustomize + name: operator-rolebinding + namespace: bpfman +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: bpfman-operator-role +subjects: + - kind: ServiceAccount + name: operator + namespace: system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding metadata: labels: app.kubernetes.io/name: rolebinding diff --git a/config/samples/bpfman.io_v1alpha1_bpfnsapplication.yaml b/config/samples/bpfman.io_v1alpha1_bpfnsapplication.yaml new file mode 100644 index 000000000..16260b119 --- /dev/null +++ b/config/samples/bpfman.io_v1alpha1_bpfnsapplication.yaml @@ -0,0 +1,56 @@ +apiVersion: bpfman.io/v1alpha1 +kind: BpfNsApplication +metadata: + labels: + app.kubernetes.io/name: bpfnsapplication + name: bpfapplication-sample + namespace: acme +spec: + # Select all nodes + nodeselector: {} + bytecode: + image: + url: quay.io/bpfman-bytecode/go-app-counter:latest + programs: + - type: TC + tc: + bpffunctionname: stats + interfaceselector: + primarynodeinterface: true + priority: 55 + direction: ingress + containers: + pods: + matchLabels: + app: nginx + - type: TCX + tcx: + bpffunctionname: tcx_stats + interfaceselector: + primarynodeinterface: true + priority: 500 + direction: ingress + containers: + pods: + matchLabels: + app: nginx + - type: Uprobe + uprobe: + bpffunctionname: uprobe_counter + func_name: malloc + target: libc + retprobe: false + containers: + pods: + matchLabels: + app: nginx + - type: XDP + xdp: + bpffunctionname: xdp_stats + interfaceselector: + primarynodeinterface: true + priority: 55 + containers: + pods: + matchLabels: + app: nginx diff --git a/config/samples/bpfman.io_v1alpha1_tc_pass_tcnsprogram.yaml b/config/samples/bpfman.io_v1alpha1_tc_pass_tcnsprogram.yaml new file mode 100644 index 000000000..b0ae0fed4 --- /dev/null +++ b/config/samples/bpfman.io_v1alpha1_tc_pass_tcnsprogram.yaml @@ -0,0 +1,33 @@ +apiVersion: bpfman.io/v1alpha1 +kind: TcNsProgram +metadata: + labels: + app.kubernetes.io/name: tcnsprogram + name: tc-containers + namespace: acme +spec: + bpffunctionname: pass + # Select all nodes + nodeselector: {} + interfaceselector: + interfaces: + - eth0 + priority: 0 + direction: ingress + bytecode: + image: + url: quay.io/bpfman-bytecode/tc_pass:latest + globaldata: + GLOBAL_u8: + - 0x01 + GLOBAL_u32: + - 0x0D + - 0x0C + - 0x0B + - 0x0A + containers: + pods: + matchLabels: + app: nginx + containernames: + - nginx diff --git a/config/samples/bpfman.io_v1alpha1_tcx_pass_tcxnsprogram.yaml b/config/samples/bpfman.io_v1alpha1_tcx_pass_tcxnsprogram.yaml new file mode 100644 index 000000000..5f8628cd5 --- /dev/null +++ b/config/samples/bpfman.io_v1alpha1_tcx_pass_tcxnsprogram.yaml @@ -0,0 +1,33 @@ +apiVersion: bpfman.io/v1alpha1 +kind: TcxNsProgram +metadata: + labels: + app.kubernetes.io/name: tcxnsprogram + name: tcx-containers + namespace: acme +spec: + bpffunctionname: tcx_next + # Select all nodes + nodeselector: {} + interfaceselector: + interfaces: + - eth0 + priority: 0 + direction: ingress + bytecode: + image: + url: quay.io/bpfman-bytecode/tcx_test:latest + globaldata: + GLOBAL_u8: + - 0x01 + GLOBAL_u32: + - 0x0D + - 0x0C + - 0x0B + - 0x0A + containers: + pods: + matchLabels: + app: nginx + containernames: + - nginx diff --git a/config/samples/bpfman.io_v1alpha1_uprobe_uprobensprogram.yaml b/config/samples/bpfman.io_v1alpha1_uprobe_uprobensprogram.yaml new file mode 100644 index 000000000..5d66ec4d1 --- /dev/null +++ b/config/samples/bpfman.io_v1alpha1_uprobe_uprobensprogram.yaml @@ -0,0 +1,33 @@ +apiVersion: bpfman.io/v1alpha1 +kind: UprobeNsProgram +metadata: + labels: + app.kubernetes.io/name: uprobensprogram + name: uprobe-example + namespace: acme +spec: + # Select all nodes + nodeselector: {} + bpffunctionname: my_uprobe + func_name: syscall + # offset: 0 # optional offset w/in function + target: libc + retprobe: false + # pid: 0 # optional pid to execute uprobe for + bytecode: + image: + url: quay.io/bpfman-bytecode/uprobe:latest + globaldata: + GLOBAL_u8: + - 0x01 + GLOBAL_u32: + - 0x0D + - 0x0C + - 0x0B + - 0x0A + containers: + pods: + matchLabels: + app: nginx + containernames: + - nginx diff --git a/config/samples/bpfman.io_v1alpha1_xdp_pass_xdpnsprogram.yaml b/config/samples/bpfman.io_v1alpha1_xdp_pass_xdpnsprogram.yaml new file mode 100644 index 000000000..ecec2eda6 --- /dev/null +++ b/config/samples/bpfman.io_v1alpha1_xdp_pass_xdpnsprogram.yaml @@ -0,0 +1,32 @@ +apiVersion: bpfman.io/v1alpha1 +kind: XdpNsProgram +metadata: + labels: + app.kubernetes.io/name: xdpnsprogram + name: xdp-ns-pass-all-nodes + namespace: acme +spec: + bpffunctionname: pass + # Select all nodes + nodeselector: {} + interfaceselector: + interfaces: + - eth0 + priority: 0 + bytecode: + image: + url: quay.io/bpfman-bytecode/xdp_pass:latest + globaldata: + GLOBAL_u8: + - 0x01 + GLOBAL_u32: + - 0x0D + - 0x0C + - 0x0B + - 0x0A + containers: + pods: + matchLabels: + app: nginx + containernames: + - nginx diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 4795266f4..c2508f5f2 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -1,11 +1,17 @@ ## Append samples you want in your CSV to this file as resources ## resources: - - bpfman.io_v1alpha1_xdp_pass_xdpprogram.yaml - - bpfman.io_v1alpha1_tracepoint_tracepointprogram.yaml - - bpfman.io_v1alpha1_tc_pass_tcprogram.yaml - - bpfman.io_v1alpha1_kprobe_kprobeprogram.yaml - - bpfman.io_v1alpha1_uprobe_uprobeprogram.yaml - bpfman.io_v1alpha1_fentry_fentryprogram.yaml - bpfman.io_v1alpha1_fexit_fexitprogram.yaml + - bpfman.io_v1alpha1_kprobe_kprobeprogram.yaml + - bpfman.io_v1alpha1_tc_pass_tcprogram.yaml + - bpfman.io_v1alpha1_tcx_pass_tcxprogram.yaml + - bpfman.io_v1alpha1_tracepoint_tracepointprogram.yaml + - bpfman.io_v1alpha1_uprobe_uprobeprogram.yaml + - bpfman.io_v1alpha1_xdp_pass_xdpprogram.yaml - bpfman.io_v1alpha1_bpfapplication.yaml + - bpfman.io_v1alpha1_tc_pass_tcnsprogram.yaml + - bpfman.io_v1alpha1_tcx_pass_tcxnsprogram.yaml + - bpfman.io_v1alpha1_uprobe_uprobensprogram.yaml + - bpfman.io_v1alpha1_xdp_pass_xdpnsprogram.yaml + - bpfman.io_v1alpha1_bpfnsapplication.yaml # +kubebuilder:scaffold:manifestskustomizesamples diff --git a/controllers/bpfman-agent/application-ns-program.go b/controllers/bpfman-agent/application-ns-program.go new file mode 100644 index 000000000..3d35dda33 --- /dev/null +++ b/controllers/bpfman-agent/application-ns-program.go @@ -0,0 +1,288 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanagent + +import ( + "context" + "fmt" + "strings" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman-operator/internal" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfnsapplications,verbs=get;list;watch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=bpfnsapplications,verbs=get;list;watch + +type BpfNsApplicationReconciler struct { + NamespaceProgramReconciler + currentApp *bpfmaniov1alpha1.BpfNsApplication + ourNode *v1.Node +} + +func (r *BpfNsApplicationReconciler) getRecType() string { + return internal.ApplicationString +} + +func (r *BpfNsApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // Initialize node and current program + r.currentApp = &bpfmaniov1alpha1.BpfNsApplication{} + r.ourNode = &v1.Node{} + r.Logger = ctrl.Log.WithName("application-ns") + r.appOwner = &bpfmaniov1alpha1.BpfNsApplication{} + r.finalizer = internal.BpfNsApplicationControllerFinalizer + r.recType = internal.ApplicationString + + r.Logger.Info("bpfman-agent enter: application-ns", "Namespace", req.Namespace, "Name", req.Name) + + // Lookup K8s node object for this bpfman-agent This should always succeed + if err := r.Get(ctx, types.NamespacedName{Namespace: v1.NamespaceAll, Name: r.NodeName}, r.ourNode); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfman-agent node %s : %v", + req.NamespacedName, err) + } + + appPrograms := &bpfmaniov1alpha1.BpfNsApplicationList{} + + opts := []client.ListOption{} + + if err := r.List(ctx, appPrograms, opts...); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting BpfNsApplicationPrograms for full reconcile %s : %v", + req.NamespacedName, err) + } + + if len(appPrograms.Items) == 0 { + r.Logger.Info("BpfNsApplicationController found no application Programs") + return ctrl.Result{Requeue: false}, nil + } + + var res ctrl.Result + var err error + var complete bool + var lastRec bpfmanReconciler[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList] + + buildProgramName := func( + app bpfmaniov1alpha1.BpfNsApplication, + prog bpfmaniov1alpha1.BpfNsApplicationProgram) string { + return app.Name + "-" + strings.ToLower(string(prog.Type)) + } + + for i, a := range appPrograms.Items { + var appProgramMap = make(map[string]bool) + for j, p := range a.Spec.Programs { + switch p.Type { + case bpfmaniov1alpha1.ProgTypeUprobe, + bpfmaniov1alpha1.ProgTypeUretprobe: + appProgramId := fmt.Sprintf("%s-%s-%s", strings.ToLower(string(p.Type)), sanitize(p.Uprobe.FunctionName), p.Uprobe.BpfFunctionName) + uprobeProgram := bpfmaniov1alpha1.UprobeNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildProgramName(a, p), + Namespace: req.Namespace, + Labels: map[string]string{internal.AppProgramId: appProgramId}}, + Spec: bpfmaniov1alpha1.UprobeNsProgramSpec{ + UprobeNsProgramInfo: *p.Uprobe, + BpfAppCommon: a.Spec.BpfAppCommon, + }, + } + rec := &UprobeNsProgramReconciler{ + NamespaceProgramReconciler: r.NamespaceProgramReconciler, + currentUprobeNsProgram: &uprobeProgram, + ourNode: r.ourNode, + } + rec.appOwner = &a + uprobeObjects := []client.Object{&uprobeProgram} + appProgramMap[appProgramId] = true + // Reconcile UprobeNsProgram or UretprobeNsProgram. + complete, res, err = r.reconcileCommon(ctx, rec, uprobeObjects) + lastRec = rec + + case bpfmaniov1alpha1.ProgTypeTC: + _, ifErr := getInterfaces(&p.TC.InterfaceSelector, r.ourNode) + if ifErr != nil { + r.Logger.Error(ifErr, "failed to get interfaces for TC NS Program", + "app program name", a.Name, "program index", j) + continue + } + appProgramId := fmt.Sprintf("%s-%s-%s", strings.ToLower(string(p.Type)), p.TC.Direction, p.TC.BpfFunctionName) + tcProgram := bpfmaniov1alpha1.TcNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildProgramName(a, p), + Namespace: req.Namespace, + Labels: map[string]string{internal.AppProgramId: appProgramId}}, + Spec: bpfmaniov1alpha1.TcNsProgramSpec{ + TcNsProgramInfo: *p.TC, + BpfAppCommon: a.Spec.BpfAppCommon, + }, + } + rec := &TcNsProgramReconciler{ + NamespaceProgramReconciler: r.NamespaceProgramReconciler, + currentTcNsProgram: &tcProgram, + ourNode: r.ourNode, + } + rec.appOwner = &a + tcObjects := []client.Object{&tcProgram} + appProgramMap[appProgramId] = true + // Reconcile TcNsProgram. + complete, res, err = r.reconcileCommon(ctx, rec, tcObjects) + lastRec = rec + + case bpfmaniov1alpha1.ProgTypeTCX: + _, ifErr := getInterfaces(&p.TCX.InterfaceSelector, r.ourNode) + if ifErr != nil { + r.Logger.Error(ifErr, "failed to get interfaces for TCX Program", + "app program name", a.Name, "program index", j) + continue + } + appProgramId := fmt.Sprintf("%s-%s-%s", strings.ToLower(string(p.Type)), p.TCX.Direction, p.TCX.BpfFunctionName) + tcxProgram := bpfmaniov1alpha1.TcxNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildProgramName(a, p), + Namespace: req.Namespace, + Labels: map[string]string{internal.AppProgramId: appProgramId}}, + Spec: bpfmaniov1alpha1.TcxNsProgramSpec{ + TcxNsProgramInfo: *p.TCX, + BpfAppCommon: a.Spec.BpfAppCommon, + }, + } + rec := &TcxNsProgramReconciler{ + NamespaceProgramReconciler: r.NamespaceProgramReconciler, + currentTcxNsProgram: &tcxProgram, + ourNode: r.ourNode, + } + rec.appOwner = &a + tcxObjects := []client.Object{&tcxProgram} + appProgramMap[appProgramId] = true + // Reconcile TcxNsProgram. + complete, res, err = r.reconcileCommon(ctx, rec, tcxObjects) + lastRec = rec + + case bpfmaniov1alpha1.ProgTypeXDP: + _, ifErr := getInterfaces(&p.XDP.InterfaceSelector, r.ourNode) + if ifErr != nil { + r.Logger.Error(ifErr, "failed to get interfaces for XDP Program", + "app program name", a.Name, "program index", j) + continue + } + appProgramId := fmt.Sprintf("%s-%s", strings.ToLower(string(p.Type)), p.XDP.BpfFunctionName) + xdpProgram := bpfmaniov1alpha1.XdpNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildProgramName(a, p), + Namespace: req.Namespace, + Labels: map[string]string{internal.AppProgramId: appProgramId}}, + Spec: bpfmaniov1alpha1.XdpNsProgramSpec{ + XdpNsProgramInfo: *p.XDP, + BpfAppCommon: a.Spec.BpfAppCommon, + }, + } + rec := &XdpNsProgramReconciler{ + NamespaceProgramReconciler: r.NamespaceProgramReconciler, + currentXdpNsProgram: &xdpProgram, + ourNode: r.ourNode, + } + rec.appOwner = &a + xdpObjects := []client.Object{&xdpProgram} + appProgramMap[appProgramId] = true + // Reconcile XdpNsProgram. + complete, res, err = r.reconcileCommon(ctx, rec, xdpObjects) + lastRec = rec + + default: + r.Logger.Error(fmt.Errorf("unsupported bpf namespaced program type"), "unsupported bpf namespaced program type", "ProgType", p.Type) + // Skip this program and continue to the next one + continue + } + + r.Logger.V(1).Info("Reconcile Application", "Application", i, "Program", j, "Name", a.Name, + "type", p.Type, "Complete", complete, "Result", res, "Error", err) + + if complete { + // We've completed reconciling this program, continue to the next one + continue + } else { + return res, err + } + } + + if complete { + bpfPrograms := &bpfmaniov1alpha1.BpfNsProgramList{} + bpfDeletedPrograms := &bpfmaniov1alpha1.BpfNsProgramList{} + // find programs that need to be deleted and delete them + opts := []client.ListOption{client.MatchingLabels{internal.BpfProgramOwner: a.Name}} + if err := r.List(ctx, bpfPrograms, opts...); err != nil { + r.Logger.Error(err, "failed to get freshPrograms for full reconcile") + return ctrl.Result{}, err + } + for _, bpfProgram := range bpfPrograms.Items { + id := bpfProgram.Labels[internal.AppProgramId] + if _, ok := appProgramMap[id]; !ok { + r.Logger.Info("Deleting BpfNsProgram", "AppProgramId", id, "BpfNsProgram", bpfProgram.Name) + bpfDeletedPrograms.Items = append(bpfDeletedPrograms.Items, bpfProgram) + } + } + // Delete BpfNsPrograms that are no longer needed + res, err = r.unLoadAndDeleteBpfProgramsList(ctx, lastRec, bpfDeletedPrograms, internal.BpfNsApplicationControllerFinalizer) + if err != nil { + r.Logger.Error(err, "failed to delete programs") + return ctrl.Result{}, err + } + // We've completed reconciling all programs for this application, continue to the next one + continue + } else { + return res, err + } + } + + return res, err +} + +// SetupWithManager sets up the controller with the Manager. +// The Bpfman-Agent should reconcile whenever a BpfNsApplication object is updated, +// load the programs to the node via bpfman, and then create a BpfNsProgram object +// to reflect per node state information. +func (r *BpfNsApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.BpfNsApplication{}, builder.WithPredicates(predicate.And(predicate.GenerationChangedPredicate{}, predicate.ResourceVersionChangedPredicate{}))). + Owns(&bpfmaniov1alpha1.BpfNsProgram{}, + builder.WithPredicates(predicate.And( + internal.BpfNsProgramTypePredicate(internal.ApplicationString), + internal.BpfProgramNodePredicate(r.NodeName)), + ), + ). + // Only trigger reconciliation if node labels change since that could + // make the BpfNsApplication no longer select the Node. Additionally only + // care about node events specific to our node + Watches( + &v1.Node{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And(predicate.LabelChangedPredicate{}, nodePredicate(r.NodeName))), + ). + // Watch for changes in Pod resources in case we are using a container selector. + Watches( + &v1.Pod{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(podOnNodePredicate(r.NodeName)), + ). + Complete(r) +} diff --git a/controllers/bpfman-agent/application-ns-program_test.go b/controllers/bpfman-agent/application-ns-program_test.go new file mode 100644 index 000000000..ec441f90d --- /dev/null +++ b/controllers/bpfman-agent/application-ns-program_test.go @@ -0,0 +1,364 @@ +package bpfmanagent + +import ( + "context" + "fmt" + "testing" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" + "github.com/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" +) + +func TestBpfNsApplicationControllerCreate(t *testing.T) { + var ( + // global config + name = "fakeAppProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + fakeNode = testutils.NewNode("fake-control-plane") + fakePodName = "my-pod" + fakeContainerName = "my-container-1" + fakePid = int64(4490) + ctx = context.TODO() + bpfUprobeFunctionName = "test" + uprobeFunctionName = "malloc" + uprobeTarget = "libc" + uprobeAppProgramId = fmt.Sprintf("%s-%s-%s", "uprobe", sanitize(uprobeFunctionName), bpfUprobeFunctionName) + uprobeBpfProg = &bpfmaniov1alpha1.BpfNsProgram{} + uprobeFakeUID = "ef71d42c-aa21-48e8-a697-82391d801a80" + uprobeOffset = 0 + uprobeRetprobe = false + uprobeAttachPoint = fmt.Sprintf("%s-%s-%s-%s", + sanitize(uprobeTarget), + sanitize(uprobeFunctionName), + fakePodName, + fakeContainerName, + ) + // xdp program config + bpfXdpFunctionName = "test" + xdpFakeInt = "eth0" + xdpAppProgramId = fmt.Sprintf("%s-%s", "xdp", bpfXdpFunctionName) + xdpBpfProg = &bpfmaniov1alpha1.BpfNsProgram{} + xdpFakeUID = "ef71d42c-aa21-48e8-a697-82391d801a82" + xdpAttachPoint = fmt.Sprintf("%s-%s-%s", + xdpFakeInt, + fakePodName, + fakeContainerName, + ) + ) + + var fakeInts = []string{xdpFakeInt} + + // A AppProgram object with metadata and spec. + App := &bpfmaniov1alpha1.BpfNsApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: bpfmaniov1alpha1.BpfNsApplicationSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + Programs: []bpfmaniov1alpha1.BpfNsApplicationProgram{ + { + Type: bpfmaniov1alpha1.ProgTypeUprobe, + Uprobe: &bpfmaniov1alpha1.UprobeNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfUprobeFunctionName, + }, + FunctionName: uprobeFunctionName, + Target: uprobeTarget, + Offset: uint64(uprobeOffset), + RetProbe: uprobeRetprobe, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + }, + }, + { + Type: bpfmaniov1alpha1.ProgTypeXDP, + XDP: &bpfmaniov1alpha1.XdpNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfXdpFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &fakeInts, + }, + Priority: 0, + ProceedOn: []bpfmaniov1alpha1.XdpProceedOnValue{bpfmaniov1alpha1.XdpProceedOnValue("pass"), + bpfmaniov1alpha1.XdpProceedOnValue("dispatcher_return"), + }, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + }, + }, + }, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, App} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, App) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsApplicationList{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsApplication{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder(). + WithStatusSubresource(App). + WithStatusSubresource(&bpfmaniov1alpha1.BpfNsProgram{}). + WithRuntimeObjects(objs...).Build() + + cli := agenttestutils.NewBpfmanClientFake() + + testContainers := FakeContainerGetter{ + containerList: &[]ContainerInfo{ + { + podName: fakePodName, + containerName: fakeContainerName, + pid: fakePid, + }, + }, + } + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + BpfmanClient: cli, + NodeName: fakeNode.Name, + Containers: &testContainers, + appOwner: App, + } + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger, so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &BpfNsApplicationReconciler{NamespaceProgramReconciler: npr, ourNode: fakeNode} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should create the bpf program object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Check the BpfNsProgram Object was created successfully + ruprobe := &UprobeNsProgramReconciler{NamespaceProgramReconciler: npr, ourNode: fakeNode} + err = r.getBpfProgram(ctx, ruprobe, name, uprobeAppProgramId, uprobeAttachPoint, uprobeBpfProg) + require.NoError(t, err) + + require.NotEmpty(t, uprobeBpfProg) + // Finalizer is written + require.Equal(t, internal.BpfNsApplicationControllerFinalizer, uprobeBpfProg.Finalizers[0]) + // owningConfig Label was correctly set + require.Equal(t, name, uprobeBpfProg.Labels[internal.BpfProgramOwner]) + // node Label was correctly set + require.Equal(t, fakeNode.Name, uprobeBpfProg.Labels[internal.K8sHostLabel]) + // uprobe target Annotation was correctly set + require.Equal(t, uprobeTarget, uprobeBpfProg.Annotations[internal.UprobeNsProgramTarget]) + // Type is set + require.Equal(t, r.getRecType(), uprobeBpfProg.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of BpfNsProgram with Fake UID since the fake API server won't + uprobeBpfProg.UID = types.UID(uprobeFakeUID) + err = cl.Update(ctx, uprobeBpfProg) + require.NoError(t, err) + + // Second reconcile should create the bpfman Load Request and update the + // BpfNsProgram object's maps field and id annotation. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + pid32 := int32(fakePid) + + // Require no requeue + require.False(t, res.Requeue) + + // do Uprobe Program + expectedLoadReq := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfUprobeFunctionName, + ProgramType: *internal.Kprobe.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(uprobeBpfProg.UID), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_UprobeAttachInfo{ + UprobeAttachInfo: &gobpfman.UprobeAttachInfo{ + FnName: &uprobeFunctionName, + Target: uprobeTarget, + Offset: uint64(uprobeOffset), + Retprobe: uprobeRetprobe, + ContainerPid: &pid32, + }, + }, + }, + } + + // Check that the BpfNsProgram's programs was correctly updated + err = r.getBpfProgram(ctx, ruprobe, name, uprobeAppProgramId, uprobeAttachPoint, uprobeBpfProg) + require.NoError(t, err) + + // prog ID should already have been set + id, err := GetID(uprobeBpfProg) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly Built + if !cmp.Equal(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) { + cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) + t.Logf("Diff %v", cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Third reconcile should set the status to loaded + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check that the BpfNsProgram's status was correctly updated + err = r.getBpfProgram(ctx, ruprobe, name, uprobeAppProgramId, uprobeAttachPoint, uprobeBpfProg) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), uprobeBpfProg.Status.Conditions[0].Type) + + // do xdp program + // First reconcile should create the bpf program object + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + rxdp := &XdpNsProgramReconciler{NamespaceProgramReconciler: npr, ourNode: fakeNode} + err = r.getBpfProgram(ctx, rxdp, name, xdpAppProgramId, xdpAttachPoint, xdpBpfProg) + require.NoError(t, err) + + require.NotEmpty(t, xdpBpfProg) + // Finalizer is written + require.Equal(t, internal.BpfNsApplicationControllerFinalizer, xdpBpfProg.Finalizers[0]) + // owningConfig Label was correctly set + require.Equal(t, name, xdpBpfProg.Labels[internal.BpfProgramOwner]) + // node Label was correctly set + require.Equal(t, fakeNode.Name, xdpBpfProg.Labels[internal.K8sHostLabel]) + // Type is set + require.Equal(t, r.getRecType(), xdpBpfProg.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of BpfNsProgram with Fake UID since the fake API server won't + xdpBpfProg.UID = types.UID(xdpFakeUID) + err = cl.Update(ctx, xdpBpfProg) + require.NoError(t, err) + + // Second reconcile should create the bpfman Load Request and update the + // BpfNsProgram object's maps field and id annotation. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + netns := fmt.Sprintf("/host/proc/%d/ns/net", fakePid) + + expectedLoadReq = &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfXdpFunctionName, + ProgramType: *internal.Xdp.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(xdpBpfProg.UID), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_XdpAttachInfo{ + XdpAttachInfo: &gobpfman.XDPAttachInfo{ + Priority: 0, + Iface: fakeInts[0], + ProceedOn: []int32{2, 31}, + Netns: &netns, + }, + }, + }, + } + + // Check that the BpfNsProgram's programs was correctly updated + err = r.getBpfProgram(ctx, rxdp, name, xdpAppProgramId, xdpAttachPoint, xdpBpfProg) + require.NoError(t, err) + + // prog ID should already have been set + id, err = GetID(xdpBpfProg) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly Built + if !cmp.Equal(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) { + cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) + t.Logf("Diff %v", cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Third reconcile should set the status to loaded + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check that the BpfNsProgram's status was correctly updated + err = r.getBpfProgram(ctx, rxdp, name, xdpAppProgramId, xdpAttachPoint, xdpBpfProg) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), xdpBpfProg.Status.Conditions[0].Type) +} diff --git a/controllers/bpfman-agent/application-program.go b/controllers/bpfman-agent/application-program.go index ac5b019ef..151e7680f 100644 --- a/controllers/bpfman-agent/application-program.go +++ b/controllers/bpfman-agent/application-program.go @@ -1,3 +1,19 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package bpfmanagent import ( @@ -21,7 +37,7 @@ import ( //+kubebuilder:rbac:groups=bpfman.io,resources=bpfapplications,verbs=get;list;watch type BpfApplicationReconciler struct { - ReconcilerCommon + ClusterProgramReconciler currentApp *bpfmaniov1alpha1.BpfApplication ourNode *v1.Node } @@ -64,6 +80,7 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque var res ctrl.Result var err error var complete bool + var lastRec bpfmanReconciler[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] buildProgramName := func( app bpfmaniov1alpha1.BpfApplication, @@ -87,15 +104,16 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque }, } rec := &FentryProgramReconciler{ - ReconcilerCommon: r.ReconcilerCommon, - currentFentryProgram: &fentryProgram, - ourNode: r.ourNode, + ClusterProgramReconciler: r.ClusterProgramReconciler, + currentFentryProgram: &fentryProgram, + ourNode: r.ourNode, } rec.appOwner = &a fentryObjects := []client.Object{&fentryProgram} appProgramMap[appProgramId] = true // Reconcile FentryProgram. complete, res, err = r.reconcileCommon(ctx, rec, fentryObjects) + lastRec = rec case bpfmaniov1alpha1.ProgTypeFexit: appProgramId := fmt.Sprintf("%s-%s-%s", strings.ToLower(string(p.Type)), sanitize(p.Fexit.FunctionName), p.Fexit.BpfFunctionName) @@ -109,15 +127,16 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque }, } rec := &FexitProgramReconciler{ - ReconcilerCommon: r.ReconcilerCommon, - currentFexitProgram: &fexitProgram, - ourNode: r.ourNode, + ClusterProgramReconciler: r.ClusterProgramReconciler, + currentFexitProgram: &fexitProgram, + ourNode: r.ourNode, } rec.appOwner = &a fexitObjects := []client.Object{&fexitProgram} appProgramMap[appProgramId] = true // Reconcile FexitProgram. complete, res, err = r.reconcileCommon(ctx, rec, fexitObjects) + lastRec = rec case bpfmaniov1alpha1.ProgTypeKprobe, bpfmaniov1alpha1.ProgTypeKretprobe: @@ -132,15 +151,16 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque }, } rec := &KprobeProgramReconciler{ - ReconcilerCommon: r.ReconcilerCommon, - currentKprobeProgram: &kprobeProgram, - ourNode: r.ourNode, + ClusterProgramReconciler: r.ClusterProgramReconciler, + currentKprobeProgram: &kprobeProgram, + ourNode: r.ourNode, } rec.appOwner = &a kprobeObjects := []client.Object{&kprobeProgram} appProgramMap[appProgramId] = true // Reconcile KprobeProgram or KpretprobeProgram. complete, res, err = r.reconcileCommon(ctx, rec, kprobeObjects) + lastRec = rec case bpfmaniov1alpha1.ProgTypeUprobe, bpfmaniov1alpha1.ProgTypeUretprobe: @@ -155,15 +175,16 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque }, } rec := &UprobeProgramReconciler{ - ReconcilerCommon: r.ReconcilerCommon, - currentUprobeProgram: &uprobeProgram, - ourNode: r.ourNode, + ClusterProgramReconciler: r.ClusterProgramReconciler, + currentUprobeProgram: &uprobeProgram, + ourNode: r.ourNode, } rec.appOwner = &a uprobeObjects := []client.Object{&uprobeProgram} appProgramMap[appProgramId] = true // Reconcile UprobeProgram or UpretprobeProgram. complete, res, err = r.reconcileCommon(ctx, rec, uprobeObjects) + lastRec = rec case bpfmaniov1alpha1.ProgTypeTracepoint: appProgramId := fmt.Sprintf("%s-%s", strings.ToLower(string(p.Type)), p.Tracepoint.BpfFunctionName) @@ -177,7 +198,7 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque }, } rec := &TracepointProgramReconciler{ - ReconcilerCommon: r.ReconcilerCommon, + ClusterProgramReconciler: r.ClusterProgramReconciler, currentTracepointProgram: &tracepointProgram, ourNode: r.ourNode, } @@ -186,6 +207,7 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque appProgramMap[appProgramId] = true // Reconcile TracepointProgram. complete, res, err = r.reconcileCommon(ctx, rec, tracepointObjects) + lastRec = rec case bpfmaniov1alpha1.ProgTypeTC: _, ifErr := getInterfaces(&p.TC.InterfaceSelector, r.ourNode) @@ -205,15 +227,16 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque }, } rec := &TcProgramReconciler{ - ReconcilerCommon: r.ReconcilerCommon, - currentTcProgram: &tcProgram, - ourNode: r.ourNode, + ClusterProgramReconciler: r.ClusterProgramReconciler, + currentTcProgram: &tcProgram, + ourNode: r.ourNode, } rec.appOwner = &a tcObjects := []client.Object{&tcProgram} appProgramMap[appProgramId] = true // Reconcile TcProgram. complete, res, err = r.reconcileCommon(ctx, rec, tcObjects) + lastRec = rec case bpfmaniov1alpha1.ProgTypeTCX: _, ifErr := getInterfaces(&p.TCX.InterfaceSelector, r.ourNode) @@ -233,15 +256,16 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque }, } rec := &TcxProgramReconciler{ - ReconcilerCommon: r.ReconcilerCommon, - currentTcxProgram: &tcxProgram, - ourNode: r.ourNode, + ClusterProgramReconciler: r.ClusterProgramReconciler, + currentTcxProgram: &tcxProgram, + ourNode: r.ourNode, } rec.appOwner = &a tcxObjects := []client.Object{&tcxProgram} appProgramMap[appProgramId] = true // Reconcile TcxProgram. complete, res, err = r.reconcileCommon(ctx, rec, tcxObjects) + lastRec = rec case bpfmaniov1alpha1.ProgTypeXDP: _, ifErr := getInterfaces(&p.XDP.InterfaceSelector, r.ourNode) @@ -261,15 +285,16 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque }, } rec := &XdpProgramReconciler{ - ReconcilerCommon: r.ReconcilerCommon, - currentXdpProgram: &xdpProgram, - ourNode: r.ourNode, + ClusterProgramReconciler: r.ClusterProgramReconciler, + currentXdpProgram: &xdpProgram, + ourNode: r.ourNode, } rec.appOwner = &a xdpObjects := []client.Object{&xdpProgram} appProgramMap[appProgramId] = true // Reconcile XdpProgram. complete, res, err = r.reconcileCommon(ctx, rec, xdpObjects) + lastRec = rec default: r.Logger.Error(fmt.Errorf("unsupported bpf program type"), "unsupported bpf program type", "ProgType", p.Type) @@ -305,7 +330,7 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque } } // Delete BpfPrograms that are no longer needed - res, err = r.unLoadAndDeleteBpfProgramsList(ctx, bpfDeletedPrograms, internal.BpfApplicationControllerFinalizer) + res, err = r.unLoadAndDeleteBpfProgramsList(ctx, lastRec, bpfDeletedPrograms, internal.BpfApplicationControllerFinalizer) if err != nil { r.Logger.Error(err, "failed to delete programs") return ctrl.Result{}, err diff --git a/controllers/bpfman-agent/application-program_test.go b/controllers/bpfman-agent/application-program_test.go index d3c0195c7..067f4c581 100644 --- a/controllers/bpfman-agent/application-program_test.go +++ b/controllers/bpfman-agent/application-program_test.go @@ -6,7 +6,6 @@ import ( "testing" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" "github.com/bpfman/bpfman-operator/internal" testutils "github.com/bpfman/bpfman-operator/internal/test-utils" @@ -105,19 +104,22 @@ func TestBpfApplicationControllerCreate(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, appOwner: App, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger, so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &BpfApplicationReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &BpfApplicationReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -135,7 +137,8 @@ func TestBpfApplicationControllerCreate(t *testing.T) { } // Check the BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, fentryAppProgramId, fentryAttachPoint, fentryBpfProg) + rfentry := &FentryProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} + err = r.getBpfProgram(ctx, rfentry, name, fentryAppProgramId, fentryAttachPoint, fentryBpfProg) require.NoError(t, err) require.NotEmpty(t, fentryBpfProg) @@ -186,11 +189,11 @@ func TestBpfApplicationControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = rc.getBpfProgram(ctx, name, fentryAppProgramId, fentryAttachPoint, fentryBpfProg) + err = r.getBpfProgram(ctx, rfentry, name, fentryAppProgramId, fentryAttachPoint, fentryBpfProg) require.NoError(t, err) // prog ID should already have been set - id, err := bpfmanagentinternal.GetID(fentryBpfProg) + id, err := GetID(fentryBpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly Built @@ -210,7 +213,7 @@ func TestBpfApplicationControllerCreate(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, fentryAppProgramId, fentryAttachPoint, fentryBpfProg) + err = r.getBpfProgram(ctx, rfentry, name, fentryAppProgramId, fentryAttachPoint, fentryBpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), fentryBpfProg.Status.Conditions[0].Type) @@ -222,7 +225,8 @@ func TestBpfApplicationControllerCreate(t *testing.T) { t.Fatalf("reconcile: (%v)", err) } - err = rc.getBpfProgram(ctx, name, kprobeAppProgramId, kprobeAttachPoint, kprobeBpfProg) + rkprobe := &KprobeProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} + err = r.getBpfProgram(ctx, rkprobe, name, kprobeAppProgramId, kprobeAttachPoint, kprobeBpfProg) require.NoError(t, err) require.NotEmpty(t, kprobeBpfProg) @@ -275,11 +279,11 @@ func TestBpfApplicationControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = rc.getBpfProgram(ctx, name, kprobeAppProgramId, kprobeAttachPoint, kprobeBpfProg) + err = r.getBpfProgram(ctx, rkprobe, name, kprobeAppProgramId, kprobeAttachPoint, kprobeBpfProg) require.NoError(t, err) // prog ID should already have been set - id, err = bpfmanagentinternal.GetID(kprobeBpfProg) + id, err = GetID(kprobeBpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly Built @@ -299,7 +303,7 @@ func TestBpfApplicationControllerCreate(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, kprobeAppProgramId, kprobeAttachPoint, kprobeBpfProg) + err = r.getBpfProgram(ctx, rkprobe, name, kprobeAppProgramId, kprobeAttachPoint, kprobeBpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), kprobeBpfProg.Status.Conditions[0].Type) diff --git a/controllers/bpfman-agent/common.go b/controllers/bpfman-agent/common.go index a18e796c8..21ca76d2c 100644 --- a/controllers/bpfman-agent/common.go +++ b/controllers/bpfman-agent/common.go @@ -25,10 +25,10 @@ import ( "time" v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" @@ -59,17 +59,40 @@ import ( //+kubebuilder:rbac:groups=bpfman.io,resources=fentryprograms/finalizers,verbs=update //+kubebuilder:rbac:groups=bpfman.io,resources=fexityprograms/finalizers,verbs=update //+kubebuilder:rbac:groups=bpfman.io,resources=bpfapplications/finalizers,verbs=update -//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch -//+kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch -//+kubebuilder:rbac:groups=core,resources=secrets,verbs=get +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfnsprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfnsprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfnsprograms/finalizers,verbs=update +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=xdpnsprograms/finalizers,verbs=update +// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch +// +kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch +// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get const ( retryDurationAgent = 5 * time.Second programDoesNotExistErr = "does not exist" ) +type BpfProg interface { + + // GetName returns the name of the current program. + GetName() string + + // GetUID returns the UID of the current program. + GetUID() types.UID + GetAnnotations() map[string]string + GetLabels() map[string]string + GetStatus() *bpfmaniov1alpha1.BpfProgramStatus + GetClientObject() client.Object +} + +type BpfProgList[T any] interface { + // bpfmaniov1alpha1.BpfProgramList | bpfmaniov1alpha1.BpfNsProgramList + + GetItems() []T +} + // ReconcilerCommon provides a skeleton for all *Program Reconcilers. -type ReconcilerCommon struct { +type ReconcilerCommon[T BpfProg, TL BpfProgList[T]] struct { client.Client Scheme *runtime.Scheme GrpcConn *grpc.ClientConn @@ -85,7 +108,24 @@ type ReconcilerCommon struct { // bpfmanReconciler defines a generic bpfProgram K8s object reconciler which can // program bpfman from user intent in the K8s CRDs. -type bpfmanReconciler interface { +type bpfmanReconciler[T BpfProg, TL BpfProgList[T]] interface { + // BPF Cluster of Namespaced Reconciler + // + // createBpfProgram creates either a BpfProgram or BpfNsProgram instance. It is pushed + // to the Kubernetes API server at a later time. + createBpfProgram( + attachPoint string, + rec bpfmanReconciler[T, TL], + annotations map[string]string, + ) (*T, error) + // getBpfList calls the Kubernetes API server to retrieve a list of BpfProgram or BpfNsProgram objects. + getBpfList(ctx context.Context, opts []client.ListOption) (*TL, error) + // updateBpfStatus calls the Kubernetes API server to write a new condition to the Status of a + // BpfProgram or BpfNsProgram object. + updateBpfStatus(ctx context.Context, bpfProgram *T, condition metav1.Condition) error + + // *Program Reconciler + // // SetupWithManager registers the reconciler with the manager and defines // which kubernetes events will trigger a reconcile. SetupWithManager(mgr ctrl.Manager) error @@ -111,12 +151,18 @@ type bpfmanReconciler interface { getProgType() internal.ProgramType // getName returns the name of the current program being reconciled. getName() string + // getNamespace returns the namespace of the current program being reconciled. + getNamespace() string + // getNoContAnnotationIndex returns the index into the annotations map for the "NoContainersOnNode" + // field. Each program type has its own unique index, and common code needs to look in the annotations + // to determine if No Containers were found. Example indexes: TcNoContainersOnNode, XdpNoContainersOnNode, ... + getNoContAnnotationIndex() string // getExpectedBpfPrograms returns the list of BpfPrograms that are expected // to be loaded on the current node. - getExpectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfProgramList, error) + getExpectedBpfPrograms(ctx context.Context) (*TL, error) // getLoadRequest returns the LoadRequest that should be sent to bpfman to // load the given BpfProgram. - getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfProgram, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) + getLoadRequest(bpfProgram *T, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) // getNode returns node object for the current node. getNode() *v1.Node // getBpfProgramCommon returns the BpfProgramCommon object for the current @@ -143,7 +189,7 @@ type bpfmanReconciler interface { // user and retry if specified. For some errors the controller may decide not to // retry. Note: This only results in calls to bpfman if we need to change // something -func (r *ReconcilerCommon) reconcileCommon(ctx context.Context, rec bpfmanReconciler, +func (r *ReconcilerCommon[T, TL]) reconcileCommon(ctx context.Context, rec bpfmanReconciler[T, TL], programs []client.Object) (bool, ctrl.Result, error) { r.Logger.V(1).Info("Start reconcileCommon()") @@ -195,18 +241,18 @@ func (r *ReconcilerCommon) reconcileCommon(ctx context.Context, rec bpfmanReconc // reconcileBpfmanPrograms ONLY reconciles the bpfman state for a single BpfProgram. // It does not interact with the k8s API in any way. -func (r *ReconcilerCommon) reconcileBpfProgram(ctx context.Context, - rec bpfmanReconciler, +func (r *ReconcilerCommon[T, TL]) reconcileBpfProgram(ctx context.Context, + rec bpfmanReconciler[T, TL], loadedBpfPrograms map[string]*gobpfman.ListResponse_ListResult, - bpfProgram *bpfmaniov1alpha1.BpfProgram, + bpfProgram *T, isNodeSelected bool, isBeingDeleted bool, mapOwnerStatus *MapOwnerParamStatus) (bpfmaniov1alpha1.BpfProgramConditionType, error) { - r.Logger.V(1).Info("enter reconcileBpfmanProgram()", "Name", bpfProgram.Name, "CurrentProgram", rec.getName()) + r.Logger.V(1).Info("enter reconcileBpfmanProgram()", "Name", (*bpfProgram).GetName(), "CurrentProgram", rec.getName()) - uuid := bpfProgram.UID - noContainersOnNode := noContainersOnNode(bpfProgram) + uuid := (*bpfProgram).GetUID() + noContainersOnNode := noContainersOnNode(bpfProgram, rec.getNoContAnnotationIndex()) loadedBpfProgram, isLoaded := loadedBpfPrograms[string(uuid)] shouldBeLoaded := bpfProgramShouldBeLoaded(isNodeSelected, isBeingDeleted, noContainersOnNode, mapOwnerStatus) @@ -215,7 +261,7 @@ func (r *ReconcilerCommon) reconcileBpfProgram(ctx context.Context, switch isLoaded { case true: // prog ID should already have been set if program is loaded - id, err := bpfmanagentinternal.GetID(bpfProgram) + id, err := GetID(bpfProgram) if err != nil { r.Logger.Error(err, "Failed to get kernel ID for BpfProgram") return bpfmaniov1alpha1.BpfProgCondNotLoaded, err @@ -230,13 +276,13 @@ func (r *ReconcilerCommon) reconcileBpfProgram(ctx context.Context, } isSame, reasons := bpfmanagentinternal.DoesProgExist(loadedBpfProgram, loadRequest) if !isSame { - r.Logger.Info("BpfProgram is in wrong state, unloading and reloading", "reason", reasons, "Name", bpfProgram.Name, "Program ID", id) + r.Logger.Info("BpfProgram is in wrong state, unloading and reloading", "reason", reasons, "Name", (*bpfProgram).GetName(), "Program ID", id) if err := bpfmanagentinternal.UnloadBpfmanProgram(ctx, r.BpfmanClient, *id); err != nil { r.Logger.Error(err, "Failed to unload eBPF Program") return bpfmaniov1alpha1.BpfProgCondNotUnloaded, err } - r.Logger.Info("Calling bpfman to load eBPF Program on Node", "Name", bpfProgram.Name) + r.Logger.Info("Calling bpfman to load eBPF Program on Node", "Name", (*bpfProgram).GetName()) r.progId, err = bpfmanagentinternal.LoadBpfmanProgram(ctx, r.BpfmanClient, loadRequest) if err != nil { r.Logger.Error(err, "Failed to load eBPF Program") @@ -249,7 +295,7 @@ func (r *ReconcilerCommon) reconcileBpfProgram(ctx context.Context, } case false: // The program is loaded but it shouldn't be loaded. - r.Logger.Info("Calling bpfman to unload eBPF Program on node", "Name", bpfProgram.Name, "Program ID", id) + r.Logger.Info("Calling bpfman to unload eBPF Program on node", "Name", (*bpfProgram).GetName(), "Program ID", id) if err := bpfmanagentinternal.UnloadBpfmanProgram(ctx, r.BpfmanClient, *id); err != nil { r.Logger.Error(err, "Failed to unload eBPF Program") return bpfmaniov1alpha1.BpfProgCondNotUnloaded, err @@ -264,7 +310,7 @@ func (r *ReconcilerCommon) reconcileBpfProgram(ctx context.Context, return bpfmaniov1alpha1.BpfProgCondBytecodeSelectorError, err } - r.Logger.Info("Calling bpfman to load eBPF Program on node", "Name", bpfProgram.Name) + r.Logger.Info("Calling bpfman to load eBPF Program on node", "Name", (*bpfProgram).GetName()) r.progId, err = bpfmanagentinternal.LoadBpfmanProgram(ctx, r.BpfmanClient, loadRequest) if err != nil { r.Logger.Error(err, "Failed to load eBPF Program") @@ -287,7 +333,7 @@ func (r *ReconcilerCommon) reconcileBpfProgram(ctx context.Context, // reconcileBpfProgramSuccessCondition returns the proper condition for a // successful reconcile of a bpfProgram based on the given parameters. -func (r *ReconcilerCommon) reconcileBpfProgramSuccessCondition( +func (r *ReconcilerCommon[T, TL]) reconcileBpfProgramSuccessCondition( isLoaded bool, shouldBeLoaded bool, isNodeSelected bool, @@ -452,7 +498,7 @@ func getInterfaces(interfaceSelector *bpfmaniov1alpha1.InterfaceSelector, ourNod // removeFinalizer removes the finalizer from the BpfProgram object if is applied, // returning if the action resulted in a kube API update or not along with any // errors. -func (r *ReconcilerCommon) removeFinalizer(ctx context.Context, o client.Object, finalizer string) bool { +func (r *ReconcilerCommon[T, TL]) removeFinalizer(ctx context.Context, o client.Object, finalizer string) bool { changed := controllerutil.RemoveFinalizer(o, finalizer) if changed { r.Logger.Info("Calling KubeAPI to remove finalizer from BpfProgram", "object name", o.GetName()) @@ -469,36 +515,42 @@ func (r *ReconcilerCommon) removeFinalizer(ctx context.Context, o client.Object, // updateStatus updates the status of a BpfProgram object if needed, returning // false if the status was already set for the given bpfProgram, meaning reconciliation // may continue. -func (r *ReconcilerCommon) updateStatus(ctx context.Context, bpfProgram *bpfmaniov1alpha1.BpfProgram, cond bpfmaniov1alpha1.BpfProgramConditionType) bool { - - r.Logger.V(1).Info("updateStatus()", "existing conds", bpfProgram.Status.Conditions, "new cond", cond) +func (r *ReconcilerCommon[T, TL]) updateStatus( + ctx context.Context, + rec bpfmanReconciler[T, TL], + bpfProgram *T, + cond bpfmaniov1alpha1.BpfProgramConditionType, +) bool { + status := (*bpfProgram).GetStatus() + r.Logger.V(1).Info("updateStatus()", "existing conds", status.Conditions, "new cond", cond) - if bpfProgram.Status.Conditions != nil { - numConditions := len(bpfProgram.Status.Conditions) + if status.Conditions != nil { + numConditions := len(status.Conditions) if numConditions == 1 { - if bpfProgram.Status.Conditions[0].Type == string(cond) { + if status.Conditions[0].Type == string(cond) { // No change, so just return false -- not updated return false } else { // We're changing the condition, so delete this one. The // new condition will be added below. - bpfProgram.Status.Conditions = nil + status.Conditions = nil } } else if numConditions > 1 { // We should only ever have one condition, so we shouldn't hit this // case. However, if we do, log a message, delete the existing // conditions, and add the new one below. r.Logger.Info("more than one BpfProgramCondition", "numConditions", numConditions) - bpfProgram.Status.Conditions = nil + status.Conditions = nil } // if numConditions == 0, just add the new condition below. } - meta.SetStatusCondition(&bpfProgram.Status.Conditions, cond.Condition()) + //meta.SetStatusCondition(&status.Conditions, cond.Condition()) - r.Logger.Info("Calling KubeAPI to update BpfProgram condition", "Name", bpfProgram.Name, "condition", cond.Condition().Type) - if err := r.Status().Update(ctx, bpfProgram); err != nil { + r.Logger.Info("Calling KubeAPI to update BpfProgram condition", "Name", (*bpfProgram).GetName(), "condition", cond.Condition().Type) + //if err := r.Status().Update(ctx, (*bpfProgram).GetClientObject()); err != nil { + if err := rec.updateBpfStatus(ctx, bpfProgram, cond.Condition()); err != nil { r.Logger.Error(err, "failed to set BpfProgram object status") } @@ -506,8 +558,10 @@ func (r *ReconcilerCommon) updateStatus(ctx context.Context, bpfProgram *bpfmani return true } -func statusContains(bpfProgram *bpfmaniov1alpha1.BpfProgram, cond bpfmaniov1alpha1.BpfProgramConditionType) bool { - for _, c := range bpfProgram.Status.Conditions { +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func statusContains[T BpfProg](bpfProgram *T, cond bpfmaniov1alpha1.BpfProgramConditionType) bool { + status := (*bpfProgram).GetStatus() + for _, c := range status.Conditions { if c.Type == string(cond) { return true } @@ -520,10 +574,8 @@ type bpfProgKey struct { attachPoint string } -func (r *ReconcilerCommon) getExistingBpfPrograms(ctx context.Context, - rec bpfmanReconciler) (map[bpfProgKey]bpfmaniov1alpha1.BpfProgram, error) { - - bpfProgramList := &bpfmaniov1alpha1.BpfProgramList{} +func (r *ReconcilerCommon[T, TL]) getExistingBpfPrograms(ctx context.Context, + rec bpfmanReconciler[T, TL]) (map[bpfProgKey]T, error) { // Only list bpfPrograms for this *Program and the controller's node opts := []client.ListOption{ @@ -534,13 +586,13 @@ func (r *ReconcilerCommon) getExistingBpfPrograms(ctx context.Context, }, } - err := r.List(ctx, bpfProgramList, opts...) + bpfProgramList, err := rec.getBpfList(ctx, opts) if err != nil { return nil, err } - existingBpfPrograms := map[bpfProgKey]bpfmaniov1alpha1.BpfProgram{} - for _, bpfProg := range bpfProgramList.Items { + existingBpfPrograms := map[bpfProgKey]T{} + for _, bpfProg := range (*bpfProgramList).GetItems() { key := bpfProgKey{ appProgId: bpfProg.GetLabels()[internal.AppProgramId], attachPoint: bpfProg.GetAnnotations()[internal.BpfProgramAttachPoint], @@ -556,46 +608,6 @@ func generateUniqueName(baseName string) string { return fmt.Sprintf("%s-%s", baseName, uuid[:8]) } -// createBpfProgram moves some shared logic for building bpfProgram objects -// into a central location. -func (r *ReconcilerCommon) createBpfProgram( - attachPoint string, - rec bpfmanReconciler, - annotations map[string]string) (*bpfmaniov1alpha1.BpfProgram, error) { - - r.Logger.V(1).Info("createBpfProgram()", "Name", attachPoint, - "Owner", rec.getOwner().GetName(), "OwnerType", rec.getRecType(), "Name", rec.getName()) - - if annotations == nil { - annotations = make(map[string]string) - } - annotations[internal.BpfProgramAttachPoint] = attachPoint - - bpfProg := &bpfmaniov1alpha1.BpfProgram{ - ObjectMeta: metav1.ObjectMeta{ - Name: generateUniqueName(rec.getName()), - Finalizers: []string{rec.getFinalizer()}, - Labels: map[string]string{ - internal.BpfProgramOwner: rec.getOwner().GetName(), - internal.AppProgramId: rec.getAppProgramId(), - internal.K8sHostLabel: r.NodeName, - }, - Annotations: annotations, - }, - Spec: bpfmaniov1alpha1.BpfProgramSpec{ - Type: rec.getRecType(), - }, - Status: bpfmaniov1alpha1.BpfProgramStatus{Conditions: []metav1.Condition{}}, - } - - // Make the corresponding BpfProgramConfig the owner - if err := ctrl.SetControllerReference(rec.getOwner(), bpfProg, r.Scheme); err != nil { - return nil, fmt.Errorf("failed to set BpfProgram object owner reference: %v", err) - } - - return bpfProg, nil -} - // Programs may be deleted for one of two reasons. The first is that the global // *Program object is being deleted. The second is that the something has // changed on the node that is causing the need to remove individual @@ -613,10 +625,10 @@ func (r *ReconcilerCommon) createBpfProgram( // // For the second case, we need to do the first 2 steps, and then explicitly // delete the bpfPrograms that are no longer needed. -func (r *ReconcilerCommon) handleProgDelete( +func (r *ReconcilerCommon[T, TL]) handleProgDelete( ctx context.Context, - rec bpfmanReconciler, - existingBpfPrograms map[bpfProgKey]bpfmaniov1alpha1.BpfProgram, + rec bpfmanReconciler[T, TL], + existingBpfPrograms map[bpfProgKey]T, loadedBpfPrograms map[string]*gobpfman.ListResponse_ListResult, isNodeSelected bool, isBeingDeleted bool, @@ -625,7 +637,7 @@ func (r *ReconcilerCommon) handleProgDelete( r.Logger.V(1).Info("handleProgDelete()", "isBeingDeleted", isBeingDeleted, "isNodeSelected", isNodeSelected, "mapOwnerStatus", mapOwnerStatus) for _, bpfProgram := range existingBpfPrograms { - r.Logger.V(1).Info("Deleting BpfProgram", "Name", bpfProgram.Name) + r.Logger.V(1).Info("Deleting BpfProgram", "Name", bpfProgram.GetName()) // Reconcile the bpfProgram if error write condition and exit with // retry. cond, err := r.reconcileBpfProgram(ctx, @@ -637,11 +649,11 @@ func (r *ReconcilerCommon) handleProgDelete( mapOwnerStatus, ) if err != nil { - r.updateStatus(ctx, &bpfProgram, cond) + r.updateStatus(ctx, rec, &bpfProgram, cond) return internal.Requeue, fmt.Errorf("failed to delete program from bpfman: %v", err) } - if r.removeFinalizer(ctx, &bpfProgram, rec.getFinalizer()) { + if r.removeFinalizer(ctx, bpfProgram.GetClientObject(), rec.getFinalizer()) { return internal.Updated, nil } @@ -649,7 +661,7 @@ func (r *ReconcilerCommon) handleProgDelete( // We're deleting these programs because the *Program is being // deleted, so update the status and the program will be deleted // when the owner is deleted. - if r.updateStatus(ctx, &bpfProgram, cond) { + if r.updateStatus(ctx, rec, &bpfProgram, cond) { return internal.Updated, nil } } else { @@ -657,8 +669,8 @@ func (r *ReconcilerCommon) handleProgDelete( // to changes that caused the containers to not be selected anymore. // So, explicitly delete them. opts := client.DeleteOptions{} - r.Logger.Info("Calling KubeAPI to delete BpfProgram", "Name", bpfProgram.Name, "Owner", bpfProgram.GetName()) - if err := r.Delete(ctx, &bpfProgram, &opts); err != nil { + r.Logger.Info("Calling KubeAPI to delete BpfProgram", "Name", bpfProgram.GetName(), "Owner", bpfProgram.GetName()) + if err := r.Delete(ctx, bpfProgram.GetClientObject(), &opts); err != nil { return internal.Requeue, fmt.Errorf("failed to delete BpfProgram object: %v", err) } return internal.Updated, nil @@ -673,41 +685,46 @@ func (r *ReconcilerCommon) handleProgDelete( // unLoadAndDeleteProgramsList unloads and deletes BbpPrograms when the owning // *Program or BpfApplication is not being deleted itself, but something // has changed such that the BpfPrograms are no longer needed. -func (r *ReconcilerCommon) unLoadAndDeleteBpfProgramsList(ctx context.Context, bpfProgramsList *bpfmaniov1alpha1.BpfProgramList, finalizerString string) (reconcile.Result, error) { - for _, bpfProgram := range bpfProgramsList.Items { - r.Logger.V(1).Info("Deleting BpfProgram", "Name", bpfProgram.Name) - id, err := bpfmanagentinternal.GetID(&bpfProgram) +func (r *ReconcilerCommon[T, TL]) unLoadAndDeleteBpfProgramsList( + ctx context.Context, + rec bpfmanReconciler[T, TL], + bpfProgramsList *TL, + finalizerString string, +) (reconcile.Result, error) { + for _, bpfProgram := range (*bpfProgramsList).GetItems() { + r.Logger.V(1).Info("Deleting BpfProgram", "Name", bpfProgram.GetName()) + id, err := GetID(&bpfProgram) if err != nil { r.Logger.Error(err, "Failed to get kernel ID from BpfProgram") return ctrl.Result{}, nil } if id != nil && !statusContains(&bpfProgram, bpfmaniov1alpha1.BpfProgCondUnloaded) { - r.Logger.Info("Calling bpfman to unload program on node", "Name", bpfProgram.Name, "Program ID", id) + r.Logger.Info("Calling bpfman to unload program on node", "Name", bpfProgram.GetName(), "Program ID", id) if err := bpfmanagentinternal.UnloadBpfmanProgram(ctx, r.BpfmanClient, *id); err != nil { if strings.Contains(err.Error(), programDoesNotExistErr) { - r.Logger.Info("Program not found on node", "Name", bpfProgram.Name, "Program ID", id) + r.Logger.Info("Program not found on node", "Name", bpfProgram.GetName(), "Program ID", id) } else { r.Logger.Error(err, "Failed to unload Program from bpfman") return ctrl.Result{RequeueAfter: retryDurationAgent}, nil } - if r.updateStatus(ctx, &bpfProgram, bpfmaniov1alpha1.BpfProgCondUnloaded) { + if r.updateStatus(ctx, rec, &bpfProgram, bpfmaniov1alpha1.BpfProgCondUnloaded) { return ctrl.Result{}, nil } } } - if r.updateStatus(ctx, &bpfProgram, bpfmaniov1alpha1.BpfProgCondUnloaded) { + if r.updateStatus(ctx, rec, &bpfProgram, bpfmaniov1alpha1.BpfProgCondUnloaded) { return ctrl.Result{}, nil } - if r.removeFinalizer(ctx, &bpfProgram, finalizerString) { + if r.removeFinalizer(ctx, bpfProgram.GetClientObject(), finalizerString) { return ctrl.Result{}, nil } opts := client.DeleteOptions{} - r.Logger.Info("Calling KubeAPI to delete BpfProgram", "Name", bpfProgram.Name, "Owner", bpfProgram.GetName()) - if err := r.Delete(ctx, &bpfProgram, &opts); err != nil { + r.Logger.Info("Calling KubeAPI to delete BpfProgram", "Name", bpfProgram.GetName(), "Owner", bpfProgram.GetName()) + if err := r.Delete(ctx, bpfProgram.GetClientObject(), &opts); err != nil { return ctrl.Result{RequeueAfter: retryDurationAgent}, fmt.Errorf("failed to delete BpfProgram object: %v", err) } else { // we will deal one program at a time, so we can break out of the loop @@ -721,11 +738,11 @@ func (r *ReconcilerCommon) unLoadAndDeleteBpfProgramsList(ctx context.Context, b // bpfPrograms. If a bpfProgram is expected but doesn't exist, it is created. // If an expected bpfProgram exists, it is reconciled. If a bpfProgram exists // but is not expected, it is deleted. -func (r *ReconcilerCommon) handleProgCreateOrUpdate( +func (r *ReconcilerCommon[T, TL]) handleProgCreateOrUpdate( ctx context.Context, - rec bpfmanReconciler, - existingBpfPrograms map[bpfProgKey]bpfmaniov1alpha1.BpfProgram, - expectedBpfPrograms *bpfmaniov1alpha1.BpfProgramList, + rec bpfmanReconciler[T, TL], + existingBpfPrograms map[bpfProgKey]T, + expectedBpfPrograms *TL, loadedBpfPrograms map[string]*gobpfman.ListResponse_ListResult, isNodeSelected bool, isBeingDeleted bool, @@ -735,8 +752,8 @@ func (r *ReconcilerCommon) handleProgCreateOrUpdate( isNodeSelected, "mapOwnerStatus", mapOwnerStatus) // If the *Program isn't being deleted ALWAYS create the bpfPrograms // even if the node isn't selected - for _, expectedBpfProgram := range expectedBpfPrograms.Items { - r.Logger.V(1).Info("Creating or Updating", "Name", expectedBpfProgram.Name) + for _, expectedBpfProgram := range (*expectedBpfPrograms).GetItems() { + r.Logger.V(1).Info("Creating or Updating", "Name", expectedBpfProgram.GetName()) key := bpfProgKey{ appProgId: expectedBpfProgram.GetLabels()[internal.AppProgramId], attachPoint: expectedBpfProgram.GetAnnotations()[internal.BpfProgramAttachPoint], @@ -749,8 +766,8 @@ func (r *ReconcilerCommon) handleProgCreateOrUpdate( } else { // Create a new bpfProgram Object for this program. opts := client.CreateOptions{} - r.Logger.Info("Calling KubeAPI to create BpfProgram", "Name", expectedBpfProgram.Name, "Owner", rec.getOwner().GetName()) - if err := r.Create(ctx, &expectedBpfProgram, &opts); err != nil { + r.Logger.Info("Calling KubeAPI to create BpfProgram", "Name", expectedBpfProgram.GetName(), "Owner", rec.getOwner().GetName()) + if err := r.Create(ctx, expectedBpfProgram.GetClientObject(), &opts); err != nil { return internal.Requeue, fmt.Errorf("failed to create BpfProgram object: %v", err) } return internal.Updated, nil @@ -767,7 +784,7 @@ func (r *ReconcilerCommon) handleProgCreateOrUpdate( mapOwnerStatus, ) if err != nil { - if r.updateStatus(ctx, &existingBpfProgram, cond) { + if r.updateStatus(ctx, rec, &existingBpfProgram, cond) { // Return an error the first time. return internal.Updated, fmt.Errorf("failed to reconcile bpfman program: %v", err) } @@ -778,7 +795,7 @@ func (r *ReconcilerCommon) handleProgCreateOrUpdate( cond == bpfmaniov1alpha1.BpfProgCondMapOwnerNotLoaded || cond == bpfmaniov1alpha1.BpfProgCondNoContainersOnNode { // Write NodeNodeSelected status - if r.updateStatus(ctx, &existingBpfProgram, cond) { + if r.updateStatus(ctx, rec, &existingBpfProgram, cond) { r.Logger.V(1).Info("Update condition from bpfman reconcile", "condition", cond) return internal.Updated, nil } else { @@ -789,20 +806,22 @@ func (r *ReconcilerCommon) handleProgCreateOrUpdate( // GetID() will fail if ProgramId is not in the annotations, which is expected on a // create. In this case existingId will be nil and DeepEqual() will fail and cause // annotation to be set. - existingId, _ := bpfmanagentinternal.GetID(&existingBpfProgram) + existingId, _ := GetID(&existingBpfProgram) // If bpfProgram Maps OR the program ID annotation isn't up to date just update it and return if !reflect.DeepEqual(existingId, r.progId) { - r.Logger.Info("Calling KubeAPI to update BpfProgram Object", "Id", r.progId, "Name", existingBpfProgram.Name) + r.Logger.Info("Calling KubeAPI to update BpfProgram Object", "Id", r.progId, "Name", existingBpfProgram.GetName()) // annotations should be populated on create - existingBpfProgram.Annotations[internal.IdAnnotation] = strconv.FormatUint(uint64(*r.progId), 10) - if err := r.Update(ctx, &existingBpfProgram, &client.UpdateOptions{}); err != nil { + annotations := existingBpfProgram.GetAnnotations() + annotations[internal.IdAnnotation] = strconv.FormatUint(uint64(*r.progId), 10) + + if err := r.Update(ctx, existingBpfProgram.GetClientObject(), &client.UpdateOptions{}); err != nil { return internal.Requeue, fmt.Errorf("failed to update BpfProgram's Programs: %v", err) } return internal.Updated, nil } - if r.updateStatus(ctx, &existingBpfProgram, cond) { + if r.updateStatus(ctx, rec, &existingBpfProgram, cond) { return internal.Updated, nil } } @@ -827,8 +846,8 @@ func (r *ReconcilerCommon) handleProgCreateOrUpdate( // against the K8s API. If the function returns a retry boolean and error, the // reconcile will be retried based on a default 5 second interval if the retry // boolean is set to `true`. -func (r *ReconcilerCommon) reconcileProgram(ctx context.Context, - rec bpfmanReconciler, +func (r *ReconcilerCommon[T, TL]) reconcileProgram(ctx context.Context, + rec bpfmanReconciler[T, TL], program client.Object, loadedBpfPrograms map[string]*gobpfman.ListResponse_ListResult) (internal.ReconcileResult, error) { @@ -852,7 +871,7 @@ func (r *ReconcilerCommon) reconcileProgram(ctx context.Context, // Determine if the MapOwnerSelector was set, and if so, see if the MapOwner // ID can be found. - mapOwnerStatus, err := r.processMapOwnerParam(ctx, &rec.getBpfProgramCommon().MapOwnerSelector) + mapOwnerStatus, err := r.processMapOwnerParam(ctx, rec, &rec.getBpfProgramCommon().MapOwnerSelector) if err != nil { return internal.Requeue, fmt.Errorf("failed to determine map owner: %v", err) } @@ -897,8 +916,9 @@ type MapOwnerParamStatus struct { // function returns the ID of the BpfProgram that owns the map on this node. // Found or not, this function also returns some flags (isSet, isFound, isLoaded) // to help with the processing and setting of the proper condition on the BpfProgram Object. -func (r *ReconcilerCommon) processMapOwnerParam( +func (r *ReconcilerCommon[T, TL]) processMapOwnerParam( ctx context.Context, + rec bpfmanReconciler[T, TL], selector *metav1.LabelSelector) (*MapOwnerParamStatus, error) { mapOwnerStatus := &MapOwnerParamStatus{} @@ -923,38 +943,38 @@ func (r *ReconcilerCommon) processMapOwnerParam( labelMap[key] = value } opts := []client.ListOption{labelMap} - bpfProgramList := &bpfmaniov1alpha1.BpfProgramList{} r.Logger.V(1).Info("MapOwner Labels:", "opts", opts) - err := r.List(ctx, bpfProgramList, opts...) + bpfProgramList, err := rec.getBpfList(ctx, opts) if err != nil { return mapOwnerStatus, err } // If no BpfProgram Objects were found, or more than one, then return. - if len(bpfProgramList.Items) == 0 { + items := (*bpfProgramList).GetItems() + if len(items) == 0 { return mapOwnerStatus, nil - } else if len(bpfProgramList.Items) > 1 { + } else if len(items) > 1 { return mapOwnerStatus, fmt.Errorf("MapOwnerSelector resolved to multiple BpfProgram Objects") } else { mapOwnerStatus.isFound = true // Get bpfProgram based on UID meta - prog, err := bpfmanagentinternal.GetBpfmanProgram(ctx, r.BpfmanClient, bpfProgramList.Items[0].GetUID()) + prog, err := bpfmanagentinternal.GetBpfmanProgram(ctx, r.BpfmanClient, items[0].GetUID()) if err != nil { - return nil, fmt.Errorf("failed to get bpfman program for BpfProgram with UID %s: %v", bpfProgramList.Items[0].GetUID(), err) + return nil, fmt.Errorf("failed to get bpfman program for BpfProgram with UID %s: %v", items[0].GetUID(), err) } kernelInfo := prog.GetKernelInfo() if kernelInfo == nil { - return nil, fmt.Errorf("failed to process bpfman program for BpfProgram with UID %s: %v", bpfProgramList.Items[0].GetUID(), err) + return nil, fmt.Errorf("failed to process bpfman program for BpfProgram with UID %s: %v", items[0].GetUID(), err) } mapOwnerStatus.mapOwnerId = &kernelInfo.Id // Get most recent condition from the one eBPF Program and determine // if the BpfProgram is loaded or not. - conLen := len(bpfProgramList.Items[0].Status.Conditions) + conLen := len(items[0].GetStatus().Conditions) if conLen > 0 && - bpfProgramList.Items[0].Status.Conditions[conLen-1].Type == + items[0].GetStatus().Conditions[conLen-1].Type == string(bpfmaniov1alpha1.BpfProgCondLoaded) { mapOwnerStatus.isLoaded = true } @@ -999,14 +1019,15 @@ func appProgramId(labels map[string]string) string { // getBpfProgram returns a BpfProgram object in the bpfProgram parameter based // on the given owner, appProgId, and attachPoint. If the BpfProgram is not // found, an error is returned. -func (r *ReconcilerCommon) getBpfProgram( +// +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *ReconcilerCommon[T, TL]) getBpfProgram( ctx context.Context, + rec bpfmanReconciler[T, TL], owner string, appProgId string, attachPoint string, - bpfProgram *bpfmaniov1alpha1.BpfProgram) error { - - bpfProgramList := &bpfmaniov1alpha1.BpfProgramList{} + bpfProgram *T) error { // Only list bpfPrograms for this *Program and the controller's node opts := []client.ListOption{ @@ -1017,12 +1038,12 @@ func (r *ReconcilerCommon) getBpfProgram( }, } - err := r.List(ctx, bpfProgramList, opts...) + bpfProgramList, err := rec.getBpfList(ctx, opts) if err != nil { return err } - for _, bpfProg := range bpfProgramList.Items { + for _, bpfProg := range (*bpfProgramList).GetItems() { if appProgId == bpfProg.GetLabels()[internal.AppProgramId] && attachPoint == bpfProg.GetAnnotations()[internal.BpfProgramAttachPoint] { *bpfProgram = bpfProg @@ -1032,3 +1053,19 @@ func (r *ReconcilerCommon) getBpfProgram( return fmt.Errorf("BpfProgram not found") } + +// get the program ID from a bpfProgram +func GetID[T BpfProg](p *T) (*uint32, error) { + annotations := (*p).GetAnnotations() + idString, ok := annotations[internal.IdAnnotation] + if !ok { + return nil, fmt.Errorf("failed to get program ID because no annotations") + } + + id, err := strconv.ParseUint(idString, 10, 32) + if err != nil { + return nil, fmt.Errorf("failed to parse program ID: %v", err) + } + uid := uint32(id) + return &uid, nil +} diff --git a/controllers/bpfman-agent/common_cluster.go b/controllers/bpfman-agent/common_cluster.go new file mode 100644 index 000000000..8f54514a8 --- /dev/null +++ b/controllers/bpfman-agent/common_cluster.go @@ -0,0 +1,102 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanagent + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman-operator/internal" +) + +type ClusterProgramReconciler struct { + ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] +} + +// createBpfProgram moves some shared logic for building bpfProgram objects +// into a central location. +func (r *ClusterProgramReconciler) createBpfProgram( + attachPoint string, + rec bpfmanReconciler[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList], + annotations map[string]string, +) (*bpfmaniov1alpha1.BpfProgram, error) { + + r.Logger.V(1).Info("createBpfProgram()", "Name", attachPoint, + "Owner", rec.getOwner().GetName(), "OwnerType", rec.getRecType(), "Name", rec.getName()) + + if annotations == nil { + annotations = make(map[string]string) + } + annotations[internal.BpfProgramAttachPoint] = attachPoint + + bpfProg := &bpfmaniov1alpha1.BpfProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: generateUniqueName(rec.getName()), + Finalizers: []string{rec.getFinalizer()}, + Labels: map[string]string{ + internal.BpfProgramOwner: rec.getOwner().GetName(), + internal.AppProgramId: rec.getAppProgramId(), + internal.K8sHostLabel: r.NodeName, + }, + Annotations: annotations, + }, + Spec: bpfmaniov1alpha1.BpfProgramSpec{ + Type: rec.getRecType(), + }, + Status: bpfmaniov1alpha1.BpfProgramStatus{Conditions: []metav1.Condition{}}, + } + + // Make the corresponding BpfProgramConfig the owner + if err := ctrl.SetControllerReference(rec.getOwner(), bpfProg, r.Scheme); err != nil { + return nil, fmt.Errorf("failed to bpfProgram object owner reference: %v", err) + } + + return bpfProg, nil +} + +func (r *ClusterProgramReconciler) getBpfList( + ctx context.Context, + opts []client.ListOption, +) (*bpfmaniov1alpha1.BpfProgramList, error) { + + bpfProgramList := &bpfmaniov1alpha1.BpfProgramList{} + + err := r.List(ctx, bpfProgramList, opts...) + if err != nil { + return nil, err + } + + return bpfProgramList, nil +} + +func (r *ClusterProgramReconciler) updateBpfStatus( + ctx context.Context, + bpfProgram *bpfmaniov1alpha1.BpfProgram, + condition metav1.Condition, +) error { + bpfProgram.Status.Conditions = nil + meta.SetStatusCondition(&bpfProgram.Status.Conditions, condition) + return r.Status().Update(ctx, bpfProgram) +} diff --git a/controllers/bpfman-agent/common_namespace.go b/controllers/bpfman-agent/common_namespace.go new file mode 100644 index 000000000..ad267c3c6 --- /dev/null +++ b/controllers/bpfman-agent/common_namespace.go @@ -0,0 +1,103 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanagent + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman-operator/internal" +) + +type NamespaceProgramReconciler struct { + ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList] +} + +// createBpfProgram moves some shared logic for building bpfProgram objects +// into a central location. +func (r *NamespaceProgramReconciler) createBpfProgram( + attachPoint string, + rec bpfmanReconciler[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList], + annotations map[string]string, +) (*bpfmaniov1alpha1.BpfNsProgram, error) { + + r.Logger.V(1).Info("createBpfNsProgram()", "Name", attachPoint, + "Owner", rec.getOwner().GetName(), "OwnerType", rec.getRecType(), "Name", rec.getName(), "Namespace", rec.getNamespace()) + + if annotations == nil { + annotations = make(map[string]string) + } + annotations[internal.BpfProgramAttachPoint] = attachPoint + + bpfProg := &bpfmaniov1alpha1.BpfNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: generateUniqueName(rec.getName()), + Namespace: rec.getNamespace(), + Finalizers: []string{rec.getFinalizer()}, + Labels: map[string]string{ + internal.BpfProgramOwner: rec.getOwner().GetName(), + internal.AppProgramId: rec.getAppProgramId(), + internal.K8sHostLabel: r.NodeName, + }, + Annotations: annotations, + }, + Spec: bpfmaniov1alpha1.BpfProgramSpec{ + Type: rec.getRecType(), + }, + Status: bpfmaniov1alpha1.BpfProgramStatus{Conditions: []metav1.Condition{}}, + } + + // Make the corresponding BpfProgramConfig the owner + if err := ctrl.SetControllerReference(rec.getOwner(), bpfProg, r.Scheme); err != nil { + return nil, fmt.Errorf("failed to bpfProgram object owner reference: %v", err) + } + + return bpfProg, nil +} + +func (r *NamespaceProgramReconciler) getBpfList( + ctx context.Context, + opts []client.ListOption, +) (*bpfmaniov1alpha1.BpfNsProgramList, error) { + + bpfProgramList := &bpfmaniov1alpha1.BpfNsProgramList{} + + err := r.List(ctx, bpfProgramList, opts...) + if err != nil { + return nil, err + } + + return bpfProgramList, nil +} + +func (r *NamespaceProgramReconciler) updateBpfStatus( + ctx context.Context, + bpfProgram *bpfmaniov1alpha1.BpfNsProgram, + condition metav1.Condition, +) error { + bpfProgram.Status.Conditions = nil + meta.SetStatusCondition(&bpfProgram.Status.Conditions, condition) + return r.Status().Update(ctx, bpfProgram) +} diff --git a/controllers/bpfman-agent/containers.go b/controllers/bpfman-agent/containers.go index c02f9de58..ebdf494fc 100644 --- a/controllers/bpfman-agent/containers.go +++ b/controllers/bpfman-agent/containers.go @@ -26,8 +26,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - "github.com/bpfman/bpfman-operator/internal" "github.com/buger/jsonparser" "github.com/go-logr/logr" ) @@ -42,7 +40,10 @@ type ContainerInfo struct { // should be attached so we can mock it in unit tests. type ContainerGetter interface { // Get the list of containers on this node that match the containerSelector. - GetContainers(ctx context.Context, containerSelector *bpfmaniov1alpha1.ContainerSelector, + GetContainers(ctx context.Context, + selectorNamespace string, + selectorPods metav1.LabelSelector, + selectorContainerNames *[]string, logger logr.Logger) (*[]ContainerInfo, error) } @@ -67,17 +68,19 @@ func NewRealContainerGetter(nodeName string) (*RealContainerGetter, error) { func (c *RealContainerGetter) GetContainers( ctx context.Context, - containerSelector *bpfmaniov1alpha1.ContainerSelector, + selectorNamespace string, + selectorPods metav1.LabelSelector, + selectorContainerNames *[]string, logger logr.Logger) (*[]ContainerInfo, error) { // Get the list of pods that match the selector. - podList, err := c.getPodsForNode(ctx, containerSelector) + podList, err := c.getPodsForNode(ctx, selectorNamespace, selectorPods) if err != nil { return nil, fmt.Errorf("failed to get pod list: %v", err) } // Get the list of containers in the list of pods that match the selector. - containerList, err := getContainerInfo(podList, containerSelector.ContainerNames, logger) + containerList, err := getContainerInfo(podList, selectorContainerNames, logger) if err != nil { return nil, fmt.Errorf("failed to get container info: %v", err) } @@ -89,10 +92,13 @@ func (c *RealContainerGetter) GetContainers( // getPodsForNode returns a list of pods on the given node that match the given // container selector. -func (c *RealContainerGetter) getPodsForNode(ctx context.Context, - containerSelector *bpfmaniov1alpha1.ContainerSelector) (*v1.PodList, error) { +func (c *RealContainerGetter) getPodsForNode( + ctx context.Context, + selectorNamespace string, + selectorPods metav1.LabelSelector, +) (*v1.PodList, error) { - selectorString := metav1.FormatLabelSelector(&containerSelector.Pods) + selectorString := metav1.FormatLabelSelector(&selectorPods) if selectorString == "" { return nil, fmt.Errorf("error parsing selector: %v", selectorString) @@ -106,7 +112,7 @@ func (c *RealContainerGetter) getPodsForNode(ctx context.Context, listOptions.LabelSelector = selectorString } - podList, err := c.clientSet.CoreV1().Pods(containerSelector.Namespace).List(ctx, listOptions) + podList, err := c.clientSet.CoreV1().Pods(selectorNamespace).List(ctx, listOptions) if err != nil { return nil, fmt.Errorf("error getting pod list: %v", err) } @@ -231,12 +237,13 @@ func getContainerInfo(podList *v1.PodList, containerNames *[]string, logger logr // Check if the annotation is set to indicate that no containers on this node // matched the container selector. -func noContainersOnNode(bpfProgram *bpfmaniov1alpha1.BpfProgram) bool { +func noContainersOnNode[T BpfProg](bpfProgram *T, annotationIndex string) bool { if bpfProgram == nil { return false } - noContainersOnNode, ok := bpfProgram.Annotations[internal.UprobeNoContainersOnNode] + annotations := (*bpfProgram).GetAnnotations() + noContainersOnNode, ok := annotations[annotationIndex] if ok && noContainersOnNode == "true" { return true } diff --git a/controllers/bpfman-agent/fentry-program.go b/controllers/bpfman-agent/fentry-program.go index 02545224c..4a6fed9c8 100644 --- a/controllers/bpfman-agent/fentry-program.go +++ b/controllers/bpfman-agent/fentry-program.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanagent import ( @@ -39,7 +41,7 @@ import ( // BpfProgramReconciler reconciles a BpfProgram object type FentryProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler currentFentryProgram *bpfmaniov1alpha1.FentryProgram ourNode *v1.Node } @@ -68,6 +70,14 @@ func (r *FentryProgramReconciler) getName() string { return r.currentFentryProgram.Name } +func (r *FentryProgramReconciler) getNamespace() string { + return r.currentFentryProgram.Namespace +} + +func (r *FentryProgramReconciler) getNoContAnnotationIndex() string { + return internal.FentryNoContainersOnNode +} + func (r *FentryProgramReconciler) getNode() *v1.Node { return r.ourNode } diff --git a/controllers/bpfman-agent/fentry-program_test.go b/controllers/bpfman-agent/fentry-program_test.go index 1a5384162..a2a4e0c52 100644 --- a/controllers/bpfman-agent/fentry-program_test.go +++ b/controllers/bpfman-agent/fentry-program_test.go @@ -24,7 +24,6 @@ import ( "google.golang.org/protobuf/testing/protocmp" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" internal "github.com/bpfman/bpfman-operator/internal" testutils "github.com/bpfman/bpfman-operator/internal/test-utils" @@ -92,18 +91,22 @@ func TestFentryProgramControllerCreate(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &FentryProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &FentryProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -121,7 +124,7 @@ func TestFentryProgramControllerCreate(t *testing.T) { } // Check the BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.NotEmpty(t, bpfProg) @@ -170,11 +173,11 @@ func TestFentryProgramControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) // prog ID should already have been set - id, err := bpfmanagentinternal.GetID(bpfProg) + id, err := GetID(bpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly Built @@ -194,7 +197,7 @@ func TestFentryProgramControllerCreate(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) diff --git a/controllers/bpfman-agent/fexit-program.go b/controllers/bpfman-agent/fexit-program.go index f1799d80a..2690aaf7b 100644 --- a/controllers/bpfman-agent/fexit-program.go +++ b/controllers/bpfman-agent/fexit-program.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanagent import ( @@ -39,7 +41,7 @@ import ( // BpfProgramReconciler reconciles a BpfProgram object type FexitProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler currentFexitProgram *bpfmaniov1alpha1.FexitProgram ourNode *v1.Node } @@ -68,6 +70,14 @@ func (r *FexitProgramReconciler) getName() string { return r.currentFexitProgram.Name } +func (r *FexitProgramReconciler) getNamespace() string { + return r.currentFexitProgram.Namespace +} + +func (r *FexitProgramReconciler) getNoContAnnotationIndex() string { + return internal.FexitNoContainersOnNode +} + func (r *FexitProgramReconciler) getNode() *v1.Node { return r.ourNode } diff --git a/controllers/bpfman-agent/fexit-program_test.go b/controllers/bpfman-agent/fexit-program_test.go index a424384d5..87812a029 100644 --- a/controllers/bpfman-agent/fexit-program_test.go +++ b/controllers/bpfman-agent/fexit-program_test.go @@ -24,7 +24,6 @@ import ( "google.golang.org/protobuf/testing/protocmp" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" internal "github.com/bpfman/bpfman-operator/internal" testutils "github.com/bpfman/bpfman-operator/internal/test-utils" @@ -92,18 +91,21 @@ func TestFexitProgramControllerCreate(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &FexitProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &FexitProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -121,7 +123,7 @@ func TestFexitProgramControllerCreate(t *testing.T) { } // Check the BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.NotEmpty(t, bpfProg) @@ -170,11 +172,11 @@ func TestFexitProgramControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) // prog ID should already have been set - id, err := bpfmanagentinternal.GetID(bpfProg) + id, err := GetID(bpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly Built @@ -194,7 +196,7 @@ func TestFexitProgramControllerCreate(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) diff --git a/controllers/bpfman-agent/internal/bpfman-core.go b/controllers/bpfman-agent/internal/bpfman-core.go index 864e453b3..b068a9d87 100644 --- a/controllers/bpfman-agent/internal/bpfman-core.go +++ b/controllers/bpfman-agent/internal/bpfman-core.go @@ -19,7 +19,6 @@ package internal import ( "context" "fmt" - "strconv" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" "github.com/bpfman/bpfman-operator/internal" @@ -147,7 +146,7 @@ func ListBpfmanPrograms(ctx context.Context, bpfmanClient gobpfman.BpfmanClient, if uuid, ok := metadata[internal.UuidMetadataKey]; ok { out[uuid] = result } else { - return nil, fmt.Errorf("Unable to get uuid from program metadata") + return nil, fmt.Errorf("unable to get uuid from program metadata") } } } @@ -205,18 +204,3 @@ func Build_kernel_info_annotations(p *gobpfman.ListResponse_ListResult) map[stri } return nil } - -// get the program ID from a bpfProgram -func GetID(p *bpfmaniov1alpha1.BpfProgram) (*uint32, error) { - idString, ok := p.Annotations[internal.IdAnnotation] - if !ok { - return nil, fmt.Errorf("failed to get program ID because no annotations") - } - - id, err := strconv.ParseUint(idString, 10, 32) - if err != nil { - return nil, fmt.Errorf("failed to parse program ID: %v", err) - } - uid := uint32(id) - return &uid, nil -} diff --git a/controllers/bpfman-agent/kprobe-program.go b/controllers/bpfman-agent/kprobe-program.go index 8b18400b0..fb8b9fa82 100644 --- a/controllers/bpfman-agent/kprobe-program.go +++ b/controllers/bpfman-agent/kprobe-program.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanagent import ( @@ -39,7 +41,7 @@ import ( // BpfProgramReconciler reconciles a BpfProgram object type KprobeProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler currentKprobeProgram *bpfmaniov1alpha1.KprobeProgram ourNode *v1.Node } @@ -68,6 +70,14 @@ func (r *KprobeProgramReconciler) getName() string { return r.currentKprobeProgram.Name } +func (r *KprobeProgramReconciler) getNamespace() string { + return r.currentKprobeProgram.Namespace +} + +func (r *KprobeProgramReconciler) getNoContAnnotationIndex() string { + return internal.KprobeNoContainersOnNode +} + func (r *KprobeProgramReconciler) getNode() *v1.Node { return r.ourNode } diff --git a/controllers/bpfman-agent/kprobe-program_test.go b/controllers/bpfman-agent/kprobe-program_test.go index 96f1a3744..7c477b1be 100644 --- a/controllers/bpfman-agent/kprobe-program_test.go +++ b/controllers/bpfman-agent/kprobe-program_test.go @@ -24,7 +24,6 @@ import ( "google.golang.org/protobuf/testing/protocmp" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" internal "github.com/bpfman/bpfman-operator/internal" testutils "github.com/bpfman/bpfman-operator/internal/test-utils" @@ -97,18 +96,21 @@ func TestKprobeProgramControllerCreate(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &KprobeProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &KprobeProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -126,7 +128,7 @@ func TestKprobeProgramControllerCreate(t *testing.T) { } // Check the BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.NotEmpty(t, bpfProg) @@ -178,11 +180,11 @@ func TestKprobeProgramControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) // prog ID should already have been set - id, err := bpfmanagentinternal.GetID(bpfProg) + id, err := GetID(bpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly Built @@ -202,7 +204,7 @@ func TestKprobeProgramControllerCreate(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) diff --git a/controllers/bpfman-agent/tc-ns-program.go b/controllers/bpfman-agent/tc-ns-program.go new file mode 100644 index 000000000..f0d9a136c --- /dev/null +++ b/controllers/bpfman-agent/tc-ns-program.go @@ -0,0 +1,299 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanagent + +import ( + "context" + "fmt" + "strconv" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" + "github.com/bpfman/bpfman-operator/internal" + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=tcnsprograms,verbs=get;list;watch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=tcnsprograms,verbs=get;list;watch + +// TcNsProgramReconciler reconciles a TcNsProgram object by creating multiple +// BpfNsProgram objects and managing bpfman for each one. +type TcNsProgramReconciler struct { + NamespaceProgramReconciler + currentTcNsProgram *bpfmaniov1alpha1.TcNsProgram + interfaces []string + ourNode *v1.Node +} + +func (r *TcNsProgramReconciler) getFinalizer() string { + return r.finalizer +} + +func (r *TcNsProgramReconciler) getOwner() metav1.Object { + if r.appOwner == nil { + return r.currentTcNsProgram + } else { + return r.appOwner + } +} + +func (r *TcNsProgramReconciler) getRecType() string { + return r.recType +} + +func (r *TcNsProgramReconciler) getProgType() internal.ProgramType { + return internal.Tc +} + +func (r *TcNsProgramReconciler) getName() string { + return r.currentTcNsProgram.Name +} + +func (r *TcNsProgramReconciler) getNamespace() string { + return r.currentTcNsProgram.Namespace +} + +func (r *TcNsProgramReconciler) getNoContAnnotationIndex() string { + return internal.TcNsNoContainersOnNode +} + +func (r *TcNsProgramReconciler) getNode() *v1.Node { + return r.ourNode +} + +func (r *TcNsProgramReconciler) getBpfProgramCommon() *bpfmaniov1alpha1.BpfProgramCommon { + return &r.currentTcNsProgram.Spec.BpfProgramCommon +} + +func (r *TcNsProgramReconciler) getNodeSelector() *metav1.LabelSelector { + return &r.currentTcNsProgram.Spec.NodeSelector +} + +func (r *TcNsProgramReconciler) getBpfGlobalData() map[string][]byte { + return r.currentTcNsProgram.Spec.GlobalData +} + +func (r *TcNsProgramReconciler) getAppProgramId() string { + return appProgramId(r.currentTcNsProgram.GetLabels()) +} + +func (r *TcNsProgramReconciler) setCurrentProgram(program client.Object) error { + var err error + var ok bool + + r.currentTcNsProgram, ok = program.(*bpfmaniov1alpha1.TcNsProgram) + if !ok { + return fmt.Errorf("failed to cast program to TcNsProgram") + } + + r.interfaces, err = getInterfaces(&r.currentTcNsProgram.Spec.InterfaceSelector, r.ourNode) + if err != nil { + return fmt.Errorf("failed to get interfaces for TcNsProgram: %v", err) + } + + return nil +} + +// SetupWithManager sets up the controller with the Manager. +// The Bpfman-Agent should reconcile whenever a TcNsProgram is updated, +// load the program to the node via bpfman, and then create BpfNsProgram object(s) +// to reflect per node state information. +func (r *TcNsProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.TcNsProgram{}, builder.WithPredicates(predicate.And( + predicate.GenerationChangedPredicate{}, + predicate.ResourceVersionChangedPredicate{}), + ), + ). + Owns(&bpfmaniov1alpha1.BpfNsProgram{}, + builder.WithPredicates(predicate.And( + internal.BpfNsProgramTypePredicate(internal.Tc.String()), + internal.BpfProgramNodePredicate(r.NodeName)), + ), + ). + // Only trigger reconciliation if node labels change since that could + // make the TcNsProgram no longer select the Node. Additionally only + // care about events specific to our node + Watches( + &v1.Node{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And(predicate.LabelChangedPredicate{}, nodePredicate(r.NodeName))), + ). + // Watch for changes in Pod resources in case we are using a container selector. + Watches( + &v1.Pod{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(podOnNodePredicate(r.NodeName)), + ). + Complete(r) +} + +func (r *TcNsProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfNsProgramList, error) { + progs := &bpfmaniov1alpha1.BpfNsProgramList{} + + // There is a container selector, so see if there are any matching + // containers on this node. + containerInfo, err := r.Containers.GetContainers( + ctx, + r.getNamespace(), + r.currentTcNsProgram.Spec.Containers.Pods, + r.currentTcNsProgram.Spec.Containers.ContainerNames, + r.Logger, + ) + if err != nil { + return nil, fmt.Errorf("failed to get container pids: %v", err) + } + + if containerInfo == nil || len(*containerInfo) == 0 { + // There were no errors, but the container selector didn't + // select any containers on this node. + for _, iface := range r.interfaces { + attachPoint := fmt.Sprintf("%s-%s-%s", + iface, + r.currentTcNsProgram.Spec.Direction, + "no-containers-on-node", + ) + + annotations := map[string]string{ + internal.TcNsProgramInterface: iface, + internal.TcNsNoContainersOnNode: "true", + } + + prog, err := r.createBpfProgram(attachPoint, r, annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfNsProgram %s: %v", attachPoint, err) + } + + progs.Items = append(progs.Items, *prog) + } + } else { + // Containers were found, so create BpfNsPrograms. + for i := range *containerInfo { + container := (*containerInfo)[i] + for _, iface := range r.interfaces { + attachPoint := fmt.Sprintf("%s-%s-%s-%s", + iface, + r.currentTcNsProgram.Spec.Direction, + container.podName, + container.containerName, + ) + + annotations := map[string]string{ + internal.TcNsProgramInterface: iface, + internal.TcNsContainerPid: strconv.FormatInt(container.pid, 10), + } + + prog, err := r.createBpfProgram(attachPoint, r, annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfNsProgram %s: %v", attachPoint, err) + } + + progs.Items = append(progs.Items, *prog) + } + } + } + + return progs, nil +} + +func (r *TcNsProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // Initialize node and current program + r.currentTcNsProgram = &bpfmaniov1alpha1.TcNsProgram{} + r.finalizer = internal.TcNsProgramControllerFinalizer + r.recType = internal.Tc.String() + r.ourNode = &v1.Node{} + r.Logger = ctrl.Log.WithName("tc-ns") + + r.Logger.Info("bpfman-agent enter: tc-ns", "Namespace", req.Namespace, "Name", req.Name) + + // Lookup K8s node object for this bpfman-agent This should always succeed + if err := r.Get(ctx, types.NamespacedName{Namespace: v1.NamespaceAll, Name: r.NodeName}, r.ourNode); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfman-agent node %s : %v", + req.NamespacedName, err) + } + + tcPrograms := &bpfmaniov1alpha1.TcNsProgramList{} + + opts := []client.ListOption{} + + if err := r.List(ctx, tcPrograms, opts...); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting TcNsPrograms for full reconcile %s : %v", + req.NamespacedName, err) + } + + if len(tcPrograms.Items) == 0 { + r.Logger.Info("TcNsProgramController found no TC Programs") + return ctrl.Result{Requeue: false}, nil + } + + // Create a list of tc programs to pass into reconcileCommon() + var tcObjects []client.Object = make([]client.Object, len(tcPrograms.Items)) + for i := range tcPrograms.Items { + tcObjects[i] = &tcPrograms.Items[i] + } + + // Reconcile each TcNsProgram. + _, result, err := r.reconcileCommon(ctx, r, tcObjects) + return result, err +} + +func (r *TcNsProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfNsProgram, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) { + bytecode, err := bpfmanagentinternal.GetBytecode(r.Client, &r.currentTcNsProgram.Spec.ByteCode) + if err != nil { + return nil, fmt.Errorf("failed to process bytecode selector: %v", err) + } + + attachInfo := &gobpfman.TCAttachInfo{ + Priority: r.currentTcNsProgram.Spec.Priority, + Iface: bpfProgram.Annotations[internal.TcNsProgramInterface], + Direction: r.currentTcNsProgram.Spec.Direction, + ProceedOn: tcProceedOnToInt(r.currentTcNsProgram.Spec.ProceedOn), + } + + containerPidStr, ok := bpfProgram.Annotations[internal.TcNsContainerPid] + if ok { + netns := fmt.Sprintf("/host/proc/%s/ns/net", containerPidStr) + attachInfo.Netns = &netns + } + + loadRequest := gobpfman.LoadRequest{ + Bytecode: bytecode, + Name: r.currentTcNsProgram.Spec.BpfFunctionName, + ProgramType: uint32(internal.Tc), + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_TcAttachInfo{ + TcAttachInfo: attachInfo, + }, + }, + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.getOwner().GetName()}, + GlobalData: r.currentTcNsProgram.Spec.GlobalData, + MapOwnerId: mapOwnerId, + } + + return &loadRequest, nil +} diff --git a/controllers/bpfman-agent/tc-ns-program_test.go b/controllers/bpfman-agent/tc-ns-program_test.go new file mode 100644 index 000000000..3ed6a3df5 --- /dev/null +++ b/controllers/bpfman-agent/tc-ns-program_test.go @@ -0,0 +1,556 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanagent + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + + internal "github.com/bpfman/bpfman-operator/internal" + + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestTcNsProgramControllerCreate(t *testing.T) { + var ( + name = "fakeTcNsProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + direction = "ingress" + fakeNode = testutils.NewNode("fake-control-plane") + fakePodName = "my-pod" + fakeContainerName = "my-container-1" + fakePid = int64(20984) + fakeInt = "eth0" + ctx = context.TODO() + appProgramId = "" + bpfProg = &bpfmaniov1alpha1.BpfNsProgram{} + fakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81" + attachPoint = fmt.Sprintf("%s-%s-%s-%s", + fakeInt, + direction, + fakePodName, + fakeContainerName, + ) + ) + // A TcNsProgram object with metadata and spec. + tc := &bpfmaniov1alpha1.TcNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: bpfmaniov1alpha1.TcNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + TcNsProgramInfo: bpfmaniov1alpha1.TcNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &[]string{fakeInt}, + }, + Priority: 0, + Direction: direction, + ProceedOn: []bpfmaniov1alpha1.TcProceedOnValue{ + bpfmaniov1alpha1.TcProceedOnValue("pipe"), + bpfmaniov1alpha1.TcProceedOnValue("dispatcher_return"), + }, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": fakePodName, + }, + }, + ContainerNames: &[]string{fakeContainerName}, + }, + }, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, tc} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, tc) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.TcNsProgramList{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(tc).WithStatusSubresource(&bpfmaniov1alpha1.BpfNsProgram{}).WithRuntimeObjects(objs...).Build() + + cli := agenttestutils.NewBpfmanClientFake() + + testContainers := FakeContainerGetter{ + containerList: &[]ContainerInfo{ + { + podName: fakePodName, + containerName: fakeContainerName, + pid: fakePid, + }, + }, + } + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + BpfmanClient: cli, + NodeName: fakeNode.Name, + Containers: &testContainers, + } + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &TcNsProgramReconciler{NamespaceProgramReconciler: npr, ourNode: fakeNode} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should create the bpf program object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Check the BpfNsProgram Object was created successfully + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) + require.NoError(t, err) + + require.NotEmpty(t, bpfProg) + // owningConfig Label was correctly set + require.Equal(t, bpfProg.Labels[internal.BpfProgramOwner], name) + // node Label was correctly set + require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProg.Finalizers[0]) + // Type is set + require.Equal(t, r.getRecType(), bpfProg.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of BpfNsProgram with Fake UID since the fake API server won't + bpfProg.UID = types.UID(fakeUID) + err = cl.Update(ctx, bpfProg) + require.NoError(t, err) + + // Second reconcile should create the bpfman Load Request and update the + // BpfNsProgram object's 'Programs' field. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + uuid := string(bpfProg.UID) + netns := fmt.Sprintf("/host/proc/%d/ns/net", fakePid) + + expectedLoadReq := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Tc.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(uuid), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_TcAttachInfo{ + TcAttachInfo: &gobpfman.TCAttachInfo{ + Iface: fakeInt, + Priority: 0, + Direction: direction, + ProceedOn: []int32{3, 30}, + Netns: &netns, + }, + }, + }, + } + + // Check that the BpfNsProgram's programs was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) + require.NoError(t, err) + + // prog ID should already have been set + id, err := GetID(bpfProg) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly built + if !cmp.Equal(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) { + t.Logf("Diff %v", cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Third reconcile should update the BpfNsPrograms status to loaded + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check that the BpfNsProgram's status was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) +} + +func TestTcNsProgramControllerCreateMultiIntf(t *testing.T) { + var ( + name = "fakeTcNsProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + direction = "ingress" + fakeNode = testutils.NewNode("fake-control-plane") + fakePodName = "my-pod" + fakeContainerName = "my-container-1" + fakePid = int64(391) + fakeInts = []string{"eth0", "eth1"} + ctx = context.TODO() + appProgramId0 = "" + appProgramId1 = "" + bpfProgEth0 = &bpfmaniov1alpha1.BpfNsProgram{} + bpfProgEth1 = &bpfmaniov1alpha1.BpfNsProgram{} + fakeUID0 = "ef71d42c-aa21-48e8-a697-82391d801a80" + fakeUID1 = "ef71d42c-aa21-48e8-a697-82391d801a81" + attachPoint0 = fmt.Sprintf("%s-%s-%s-%s", + fakeInts[0], + direction, + fakePodName, + fakeContainerName, + ) + attachPoint1 = fmt.Sprintf("%s-%s-%s-%s", + fakeInts[1], + direction, + fakePodName, + fakeContainerName, + ) + ) + // A TcNsProgram object with metadata and spec. + tc := &bpfmaniov1alpha1.TcNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: bpfmaniov1alpha1.TcNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + TcNsProgramInfo: bpfmaniov1alpha1.TcNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &fakeInts, + }, + Priority: 0, + Direction: direction, + ProceedOn: []bpfmaniov1alpha1.TcProceedOnValue{ + bpfmaniov1alpha1.TcProceedOnValue("pipe"), + bpfmaniov1alpha1.TcProceedOnValue("dispatcher_return"), + }, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": fakePodName, + }, + }, + }, + }, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, tc} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, tc) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(tc).WithStatusSubresource(&bpfmaniov1alpha1.BpfNsProgram{}).WithRuntimeObjects(objs...).Build() + + cli := agenttestutils.NewBpfmanClientFake() + + testContainers := FakeContainerGetter{ + containerList: &[]ContainerInfo{ + { + podName: fakePodName, + containerName: fakeContainerName, + pid: fakePid, + }, + }, + } + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + BpfmanClient: cli, + NodeName: fakeNode.Name, + Containers: &testContainers, + } + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &TcNsProgramReconciler{NamespaceProgramReconciler: npr, ourNode: fakeNode} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should create the first bpf program object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Check the first BpfNsProgram Object was created successfully + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + require.NotEmpty(t, bpfProgEth0) + // owningConfig Label was correctly set + require.Equal(t, bpfProgEth0.Labels[internal.BpfProgramOwner], name) + // node Label was correctly set + require.Equal(t, bpfProgEth0.Labels[internal.K8sHostLabel], fakeNode.Name) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProgEth0.Finalizers[0]) + // Type is set + require.Equal(t, r.getRecType(), bpfProgEth0.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of BpfNsProgram with Fake UID since the fake API server won't + bpfProgEth0.UID = types.UID(fakeUID0) + err = cl.Update(ctx, bpfProgEth0) + require.NoError(t, err) + + // Second reconcile should create the bpfman Load Requests for the first BpfNsProgram and update the prog id. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Third reconcile should set the second bpf program object's status. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Fourth reconcile should create the bpfman Load Requests for the second BpfNsProgram. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Require no requeue + require.False(t, res.Requeue) + + // Check the Second BpfNsProgram Object was created successfully + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + require.NotEmpty(t, bpfProgEth1) + // owningConfig Label was correctly set + require.Equal(t, bpfProgEth1.Labels[internal.BpfProgramOwner], name) + // node Label was correctly set + require.Equal(t, bpfProgEth1.Labels[internal.K8sHostLabel], fakeNode.Name) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProgEth1.Finalizers[0]) + // Type is set + require.Equal(t, r.getRecType(), bpfProgEth1.Spec.Type) + + // Update UID of BpfNsProgram with Fake UID since the fake API server won't + bpfProgEth1.UID = types.UID(fakeUID1) + err = cl.Update(ctx, bpfProgEth1) + require.NoError(t, err) + + // Fifth reconcile should create the second BpfNsProgram. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Sixth reconcile should update the second BpfNsProgram's status. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + uuid0 := string(bpfProgEth0.UID) + netns := fmt.Sprintf("/host/proc/%d/ns/net", fakePid) + + expectedLoadReq0 := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Tc.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(uuid0), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_TcAttachInfo{ + TcAttachInfo: &gobpfman.TCAttachInfo{ + Iface: fakeInts[0], + Priority: 0, + Direction: direction, + ProceedOn: []int32{3, 30}, + Netns: &netns, + }, + }, + }, + } + + uuid1 := string(bpfProgEth1.UID) + + expectedLoadReq1 := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Tc.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(uuid1), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_TcAttachInfo{ + TcAttachInfo: &gobpfman.TCAttachInfo{ + Iface: fakeInts[1], + Priority: 0, + Direction: direction, + ProceedOn: []int32{3, 30}, + Netns: &netns, + }, + }, + }, + } + + // Check that the BpfNsProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + // prog ID should already have been set + id0, err := GetID(bpfProgEth0) + require.NoError(t, err) + + // Check that the BpfNsProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + // prog ID should already have been set + id1, err := GetID(bpfProgEth1) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly built + if !cmp.Equal(expectedLoadReq0, cli.LoadRequests[int(*id0)], protocmp.Transform()) { + t.Logf("Diff %v", cmp.Diff(expectedLoadReq0, cli.LoadRequests[int(*id0)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Check the bpfLoadRequest was correctly built + if !cmp.Equal(expectedLoadReq1, cli.LoadRequests[int(*id1)], protocmp.Transform()) { + t.Logf("Diff %v", cmp.Diff(expectedLoadReq1, cli.LoadRequests[int(*id1)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Check that the BpfNsProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + // Check that the BpfNsProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + // Third reconcile should update the BpfNsPrograms status to loaded + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check that the BpfNsProgram's status was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth0.Status.Conditions[0].Type) + + // Check that the BpfNsProgram's status was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth1.Status.Conditions[0].Type) +} diff --git a/controllers/bpfman-agent/tc-program.go b/controllers/bpfman-agent/tc-program.go index d65134e06..7baff7874 100644 --- a/controllers/bpfman-agent/tc-program.go +++ b/controllers/bpfman-agent/tc-program.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanagent import ( @@ -41,7 +43,7 @@ import ( // TcProgramReconciler reconciles a tcProgram object by creating multiple // bpfProgram objects and managing bpfman for each one. type TcProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler currentTcProgram *bpfmaniov1alpha1.TcProgram interfaces []string ourNode *v1.Node @@ -71,6 +73,14 @@ func (r *TcProgramReconciler) getName() string { return r.currentTcProgram.Name } +func (r *TcProgramReconciler) getNamespace() string { + return r.currentTcProgram.Namespace +} + +func (r *TcProgramReconciler) getNoContAnnotationIndex() string { + return internal.TcNoContainersOnNode +} + func (r *TcProgramReconciler) getNode() *v1.Node { return r.ourNode } @@ -183,7 +193,13 @@ func (r *TcProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpfm // There is a container selector, so see if there are any matching // containers on this node. - containerInfo, err := r.Containers.GetContainers(ctx, r.currentTcProgram.Spec.Containers, r.Logger) + containerInfo, err := r.Containers.GetContainers( + ctx, + r.currentTcProgram.Spec.Containers.Namespace, + r.currentTcProgram.Spec.Containers.Pods, + r.currentTcProgram.Spec.Containers.ContainerNames, + r.Logger, + ) if err != nil { return nil, fmt.Errorf("failed to get container pids: %v", err) } @@ -261,7 +277,7 @@ func (r *TcProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( r.ourNode = &v1.Node{} r.Logger = ctrl.Log.WithName("tc") - r.Logger.Info("bpfman-agent enter: TC", "Name", req.Name) + r.Logger.Info("bpfman-agent enter: tc", "Name", req.Name) // Lookup K8s node object for this bpfman-agent This should always succeed if err := r.Get(ctx, types.NamespacedName{Namespace: v1.NamespaceAll, Name: r.NodeName}, r.ourNode); err != nil { diff --git a/controllers/bpfman-agent/tc-program_test.go b/controllers/bpfman-agent/tc-program_test.go index a1b711843..f966b2a08 100644 --- a/controllers/bpfman-agent/tc-program_test.go +++ b/controllers/bpfman-agent/tc-program_test.go @@ -24,7 +24,6 @@ import ( "google.golang.org/protobuf/testing/protocmp" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" testutils "github.com/bpfman/bpfman-operator/internal/test-utils" @@ -102,18 +101,21 @@ func TestTcProgramControllerCreate(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &TcProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &TcProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -131,7 +133,7 @@ func TestTcProgramControllerCreate(t *testing.T) { } // Check the BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.NotEmpty(t, bpfProg) @@ -183,11 +185,11 @@ func TestTcProgramControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) // prog ID should already have been set - id, err := bpfmanagentinternal.GetID(bpfProg) + id, err := GetID(bpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly built @@ -206,7 +208,7 @@ func TestTcProgramControllerCreate(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) @@ -274,18 +276,21 @@ func TestTcProgramControllerCreateMultiIntf(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &TcProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &TcProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -303,7 +308,7 @@ func TestTcProgramControllerCreateMultiIntf(t *testing.T) { } // Check the first BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) require.NotEmpty(t, bpfProgEth0) @@ -351,7 +356,7 @@ func TestTcProgramControllerCreateMultiIntf(t *testing.T) { require.False(t, res.Requeue) // Check the Second BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) require.NotEmpty(t, bpfProgEth1) @@ -429,19 +434,19 @@ func TestTcProgramControllerCreateMultiIntf(t *testing.T) { } // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) // prog ID should already have been set - id0, err := bpfmanagentinternal.GetID(bpfProgEth0) + id0, err := GetID(bpfProgEth0) require.NoError(t, err) // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) // prog ID should already have been set - id1, err := bpfmanagentinternal.GetID(bpfProgEth1) + id1, err := GetID(bpfProgEth1) require.NoError(t, err) // Check the bpfLoadRequest was correctly built @@ -457,11 +462,11 @@ func TestTcProgramControllerCreateMultiIntf(t *testing.T) { } // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) // Third reconcile should update the bpfPrograms status to loaded @@ -474,13 +479,13 @@ func TestTcProgramControllerCreateMultiIntf(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth0.Status.Conditions[0].Type) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth1.Status.Conditions[0].Type) diff --git a/controllers/bpfman-agent/tcx-ns-program.go b/controllers/bpfman-agent/tcx-ns-program.go new file mode 100644 index 000000000..7546fb7ba --- /dev/null +++ b/controllers/bpfman-agent/tcx-ns-program.go @@ -0,0 +1,298 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanagent + +import ( + "context" + "fmt" + "strconv" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" + "github.com/bpfman/bpfman-operator/internal" + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=tcxnsprograms,verbs=get;list;watch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=tcxnsprograms,verbs=get;list;watch + +// TcxNsProgramReconciler reconciles a tcxNsProgram object by creating multiple +// bpfNsProgram objects and managing bpfman for each one. +type TcxNsProgramReconciler struct { + NamespaceProgramReconciler + currentTcxNsProgram *bpfmaniov1alpha1.TcxNsProgram + interfaces []string + ourNode *v1.Node +} + +func (r *TcxNsProgramReconciler) getFinalizer() string { + return r.finalizer +} + +func (r *TcxNsProgramReconciler) getOwner() metav1.Object { + if r.appOwner == nil { + return r.currentTcxNsProgram + } else { + return r.appOwner + } +} + +func (r *TcxNsProgramReconciler) getRecType() string { + return r.recType +} + +func (r *TcxNsProgramReconciler) getProgType() internal.ProgramType { + return internal.Tc +} + +func (r *TcxNsProgramReconciler) getName() string { + return r.currentTcxNsProgram.Name +} + +func (r *TcxNsProgramReconciler) getNamespace() string { + return r.currentTcxNsProgram.Namespace +} + +func (r *TcxNsProgramReconciler) getNoContAnnotationIndex() string { + return internal.TcxNsNoContainersOnNode +} + +func (r *TcxNsProgramReconciler) getNode() *v1.Node { + return r.ourNode +} + +func (r *TcxNsProgramReconciler) getBpfProgramCommon() *bpfmaniov1alpha1.BpfProgramCommon { + return &r.currentTcxNsProgram.Spec.BpfProgramCommon +} + +func (r *TcxNsProgramReconciler) getNodeSelector() *metav1.LabelSelector { + return &r.currentTcxNsProgram.Spec.NodeSelector +} + +func (r *TcxNsProgramReconciler) getBpfGlobalData() map[string][]byte { + return r.currentTcxNsProgram.Spec.GlobalData +} + +func (r *TcxNsProgramReconciler) getAppProgramId() string { + return appProgramId(r.currentTcxNsProgram.GetLabels()) +} + +func (r *TcxNsProgramReconciler) setCurrentProgram(program client.Object) error { + var err error + var ok bool + + r.currentTcxNsProgram, ok = program.(*bpfmaniov1alpha1.TcxNsProgram) + if !ok { + return fmt.Errorf("failed to cast program to TcxNsProgram") + } + + r.interfaces, err = getInterfaces(&r.currentTcxNsProgram.Spec.InterfaceSelector, r.ourNode) + if err != nil { + return fmt.Errorf("failed to get interfaces for TcxNsProgram: %v", err) + } + + return nil +} + +// SetupWithManager sets up the controller with the Manager. +// The Bpfman-Agent should reconcile whenever a TcxNsProgram is updated, +// load the program to the node via bpfman, and then create bpfNsProgram object(s) +// to reflect per node state information. +func (r *TcxNsProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.TcxNsProgram{}, builder.WithPredicates(predicate.And( + predicate.GenerationChangedPredicate{}, + predicate.ResourceVersionChangedPredicate{}), + ), + ). + Owns(&bpfmaniov1alpha1.BpfNsProgram{}, + builder.WithPredicates(predicate.And( + internal.BpfNsProgramTypePredicate(internal.TcxString), + internal.BpfProgramNodePredicate(r.NodeName)), + ), + ). + // Only trigger reconciliation if node labels change since that could + // make the TcxNsProgram no longer select the Node. Additionally only + // care about events specific to our node + Watches( + &v1.Node{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And(predicate.LabelChangedPredicate{}, nodePredicate(r.NodeName))), + ). + // Watch for changes in Pod resources in case we are using a container selector. + Watches( + &v1.Pod{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(podOnNodePredicate(r.NodeName)), + ). + Complete(r) +} + +func (r *TcxNsProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfNsProgramList, error) { + progs := &bpfmaniov1alpha1.BpfNsProgramList{} + + // There is a container selector, so see if there are any matching + // containers on this node. + containerInfo, err := r.Containers.GetContainers( + ctx, + r.getNamespace(), + r.currentTcxNsProgram.Spec.Containers.Pods, + r.currentTcxNsProgram.Spec.Containers.ContainerNames, + r.Logger, + ) + if err != nil { + return nil, fmt.Errorf("failed to get container pids: %v", err) + } + + if containerInfo == nil || len(*containerInfo) == 0 { + // There were no errors, but the container selector didn't + // select any containers on this node. + for _, iface := range r.interfaces { + attachPoint := fmt.Sprintf("%s-%s-%s", + iface, + r.currentTcxNsProgram.Spec.Direction, + "no-containers-on-node", + ) + + annotations := map[string]string{ + internal.TcxNsProgramInterface: iface, + internal.TcxNsNoContainersOnNode: "true", + } + + prog, err := r.createBpfProgram(attachPoint, r, annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfNsProgram %s: %v", attachPoint, err) + } + + progs.Items = append(progs.Items, *prog) + } + } else { + // Containers were found, so create BpfNsPrograms. + for i := range *containerInfo { + container := (*containerInfo)[i] + for _, iface := range r.interfaces { + attachPoint := fmt.Sprintf("%s-%s-%s-%s", + iface, + r.currentTcxNsProgram.Spec.Direction, + container.podName, + container.containerName, + ) + + annotations := map[string]string{ + internal.TcxNsProgramInterface: iface, + internal.TcxNsContainerPid: strconv.FormatInt(container.pid, 10), + } + + prog, err := r.createBpfProgram(attachPoint, r, annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfNsProgram %s: %v", attachPoint, err) + } + + progs.Items = append(progs.Items, *prog) + } + } + } + + return progs, nil +} + +func (r *TcxNsProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // Initialize node and current program + r.currentTcxNsProgram = &bpfmaniov1alpha1.TcxNsProgram{} + r.finalizer = internal.TcxNsProgramControllerFinalizer + r.recType = internal.TcxString + r.ourNode = &v1.Node{} + r.Logger = ctrl.Log.WithName("tcx-ns") + + r.Logger.Info("bpfman-agent enter: tcx-ns", "Namespace", req.Namespace, "Name", req.Name) + + // Lookup K8s node object for this bpfman-agent This should always succeed + if err := r.Get(ctx, types.NamespacedName{Namespace: v1.NamespaceAll, Name: r.NodeName}, r.ourNode); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfman-agent node %s : %v", + req.NamespacedName, err) + } + + tcxPrograms := &bpfmaniov1alpha1.TcxNsProgramList{} + + opts := []client.ListOption{} + + if err := r.List(ctx, tcxPrograms, opts...); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting TcxNsPrograms for full reconcile %s : %v", + req.NamespacedName, err) + } + + if len(tcxPrograms.Items) == 0 { + r.Logger.Info("TcxNsProgramController found no TCX NS Programs") + return ctrl.Result{Requeue: false}, nil + } + + // Create a list of tcx programs to pass into reconcileCommon() + var tcxObjects []client.Object = make([]client.Object, len(tcxPrograms.Items)) + for i := range tcxPrograms.Items { + tcxObjects[i] = &tcxPrograms.Items[i] + } + + // Reconcile each TcxNsProgram. + _, result, err := r.reconcileCommon(ctx, r, tcxObjects) + return result, err +} + +func (r *TcxNsProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfNsProgram, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) { + bytecode, err := bpfmanagentinternal.GetBytecode(r.Client, &r.currentTcxNsProgram.Spec.ByteCode) + if err != nil { + return nil, fmt.Errorf("failed to process bytecode selector: %v", err) + } + + attachInfo := &gobpfman.TCXAttachInfo{ + Priority: r.currentTcxNsProgram.Spec.Priority, + Iface: bpfProgram.Annotations[internal.TcxNsProgramInterface], + Direction: r.currentTcxNsProgram.Spec.Direction, + } + + containerPidStr, ok := bpfProgram.Annotations[internal.TcxNsContainerPid] + if ok { + netns := fmt.Sprintf("/host/proc/%s/ns/net", containerPidStr) + attachInfo.Netns = &netns + } + + loadRequest := gobpfman.LoadRequest{ + Bytecode: bytecode, + Name: r.currentTcxNsProgram.Spec.BpfFunctionName, + ProgramType: uint32(internal.Tc), + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_TcxAttachInfo{ + TcxAttachInfo: attachInfo, + }, + }, + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.getOwner().GetName()}, + GlobalData: r.currentTcxNsProgram.Spec.GlobalData, + MapOwnerId: mapOwnerId, + } + + return &loadRequest, nil +} diff --git a/controllers/bpfman-agent/tcx-ns-program_test.go b/controllers/bpfman-agent/tcx-ns-program_test.go new file mode 100644 index 000000000..3495407ac --- /dev/null +++ b/controllers/bpfman-agent/tcx-ns-program_test.go @@ -0,0 +1,546 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanagent + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + + internal "github.com/bpfman/bpfman-operator/internal" + + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestTcxNsProgramControllerCreate(t *testing.T) { + var ( + name = "fakeTcxProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + direction = "ingress" + fakeNode = testutils.NewNode("fake-control-plane") + fakePodName = "my-pod" + fakeContainerName = "my-container-1" + fakePid = int64(5457) + fakeInt = "eth0" + ctx = context.TODO() + appProgramId = "" + bpfProg = &bpfmaniov1alpha1.BpfNsProgram{} + fakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81" + attachPoint = fmt.Sprintf("%s-%s-%s-%s", + fakeInt, + direction, + fakePodName, + fakeContainerName, + ) + ) + // A TcxNsProgram object with metadata and spec. + tcx := &bpfmaniov1alpha1.TcxNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: bpfmaniov1alpha1.TcxNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + TcxNsProgramInfo: bpfmaniov1alpha1.TcxNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &[]string{fakeInt}, + }, + Priority: 0, + Direction: direction, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + }, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, tcx} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, tcx) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.TcxNsProgramList{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(tcx).WithStatusSubresource(&bpfmaniov1alpha1.BpfNsProgram{}).WithRuntimeObjects(objs...).Build() + + cli := agenttestutils.NewBpfmanClientFake() + + testContainers := FakeContainerGetter{ + containerList: &[]ContainerInfo{ + { + podName: fakePodName, + containerName: fakeContainerName, + pid: fakePid, + }, + }, + } + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + BpfmanClient: cli, + NodeName: fakeNode.Name, + Containers: &testContainers, + } + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &TcxNsProgramReconciler{NamespaceProgramReconciler: npr, ourNode: fakeNode} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should create the bpf program object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Check the BpfNsProgram Object was created successfully + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) + require.NoError(t, err) + + require.NotEmpty(t, bpfProg) + // owningConfig Label was correctly set + require.Equal(t, bpfProg.Labels[internal.BpfProgramOwner], name) + // node Label was correctly set + require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProg.Finalizers[0]) + // Type is set + require.Equal(t, r.getRecType(), bpfProg.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of BpfNsProgram with Fake UID since the fake API server won't + bpfProg.UID = types.UID(fakeUID) + err = cl.Update(ctx, bpfProg) + require.NoError(t, err) + + // Second reconcile should create the bpfman Load Request and update the + // BpfNsProgram object's 'Programs' field. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + uuid := string(bpfProg.UID) + netns := fmt.Sprintf("/host/proc/%d/ns/net", fakePid) + + expectedLoadReq := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Tc.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(uuid), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_TcxAttachInfo{ + TcxAttachInfo: &gobpfman.TCXAttachInfo{ + Iface: fakeInt, + Priority: tcx.Spec.Priority, + Direction: direction, + Netns: &netns, + }, + }, + }, + } + + // Check that the BpfNsProgram's programs was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) + require.NoError(t, err) + + // prog ID should already have been set + id, err := GetID(bpfProg) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly built + if !cmp.Equal(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) { + t.Logf("Diff %v", cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Third reconcile should update the BpfNsPrograms status to loaded + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check that the BpfNsProgram's status was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) +} + +func TestTcxNsProgramControllerCreateMultiIntf(t *testing.T) { + var ( + name = "fakeTcProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + direction = "ingress" + fakeNode = testutils.NewNode("fake-control-plane") + fakePodName = "my-pod" + fakeContainerName = "my-container-1" + fakePid = int64(9574) + fakeInts = []string{"eth0", "eth1"} + ctx = context.TODO() + appProgramId0 = "" + appProgramId1 = "" + bpfProgEth0 = &bpfmaniov1alpha1.BpfNsProgram{} + bpfProgEth1 = &bpfmaniov1alpha1.BpfNsProgram{} + fakeUID0 = "ef71d42c-aa21-48e8-a697-82391d801a80" + fakeUID1 = "ef71d42c-aa21-48e8-a697-82391d801a81" + attachPoint0 = fmt.Sprintf("%s-%s-%s-%s", + fakeInts[0], + direction, + fakePodName, + fakeContainerName, + ) + attachPoint1 = fmt.Sprintf("%s-%s-%s-%s", + fakeInts[1], + direction, + fakePodName, + fakeContainerName, + ) + ) + // A TcProgram object with metadata and spec. + tcx := &bpfmaniov1alpha1.TcxNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: bpfmaniov1alpha1.TcxNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + TcxNsProgramInfo: bpfmaniov1alpha1.TcxNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &fakeInts, + }, + Priority: 10, + Direction: direction, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + }, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, tcx} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, tcx) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(tcx).WithStatusSubresource(&bpfmaniov1alpha1.BpfNsProgram{}).WithRuntimeObjects(objs...).Build() + + cli := agenttestutils.NewBpfmanClientFake() + + testContainers := FakeContainerGetter{ + containerList: &[]ContainerInfo{ + { + podName: fakePodName, + containerName: fakeContainerName, + pid: fakePid, + }, + }, + } + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + BpfmanClient: cli, + NodeName: fakeNode.Name, + Containers: &testContainers, + } + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &TcxNsProgramReconciler{NamespaceProgramReconciler: npr, ourNode: fakeNode} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should create the first bpf program object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Check the first BpfNsProgram Object was created successfully + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + require.NotEmpty(t, bpfProgEth0) + // owningConfig Label was correctly set + require.Equal(t, bpfProgEth0.Labels[internal.BpfProgramOwner], name) + // node Label was correctly set + require.Equal(t, bpfProgEth0.Labels[internal.K8sHostLabel], fakeNode.Name) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProgEth0.Finalizers[0]) + // Type is set + require.Equal(t, r.getRecType(), bpfProgEth0.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of BpfNsProgram with Fake UID since the fake API server won't + bpfProgEth0.UID = types.UID(fakeUID0) + err = cl.Update(ctx, bpfProgEth0) + require.NoError(t, err) + + // Second reconcile should create the bpfman Load Requests for the first BpfNsProgram and update the prog id. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Third reconcile should set the second bpf program object's status. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Fourth reconcile should create the bpfman Load Requests for the second BpfNsProgram. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Require no requeue + require.False(t, res.Requeue) + + // Check the Second BpfNsProgram Object was created successfully + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + require.NotEmpty(t, bpfProgEth1) + // owningConfig Label was correctly set + require.Equal(t, bpfProgEth1.Labels[internal.BpfProgramOwner], name) + // node Label was correctly set + require.Equal(t, bpfProgEth1.Labels[internal.K8sHostLabel], fakeNode.Name) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProgEth1.Finalizers[0]) + // Type is set + require.Equal(t, r.getRecType(), bpfProgEth1.Spec.Type) + + // Update UID of BpfNsProgram with Fake UID since the fake API server won't + bpfProgEth1.UID = types.UID(fakeUID1) + err = cl.Update(ctx, bpfProgEth1) + require.NoError(t, err) + + // Fifth reconcile should create the second BpfNsProgram. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Sixth reconcile should update the second BpfNsProgram's status. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + uuid0 := string(bpfProgEth0.UID) + netns := fmt.Sprintf("/host/proc/%d/ns/net", fakePid) + + expectedLoadReq0 := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Tc.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(uuid0), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_TcxAttachInfo{ + TcxAttachInfo: &gobpfman.TCXAttachInfo{ + Iface: fakeInts[0], + Priority: tcx.Spec.Priority, + Direction: direction, + Netns: &netns, + }, + }, + }, + } + + uuid1 := string(bpfProgEth1.UID) + + expectedLoadReq1 := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Tc.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(uuid1), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_TcxAttachInfo{ + TcxAttachInfo: &gobpfman.TCXAttachInfo{ + Iface: fakeInts[1], + Priority: tcx.Spec.Priority, + Direction: direction, + Netns: &netns, + }, + }, + }, + } + + // Check that the BpfNsProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + // prog ID should already have been set + id0, err := GetID(bpfProgEth0) + require.NoError(t, err) + + // Check that the BpfNsProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + // prog ID should already have been set + id1, err := GetID(bpfProgEth1) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly built + if !cmp.Equal(expectedLoadReq0, cli.LoadRequests[int(*id0)], protocmp.Transform()) { + t.Logf("Diff %v", cmp.Diff(expectedLoadReq0, cli.LoadRequests[int(*id0)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Check the bpfLoadRequest was correctly built + if !cmp.Equal(expectedLoadReq1, cli.LoadRequests[int(*id1)], protocmp.Transform()) { + t.Logf("Diff %v", cmp.Diff(expectedLoadReq1, cli.LoadRequests[int(*id1)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Check that the BpfNsProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + // Check that the BpfNsProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + // Third reconcile should update the BpfNsPrograms status to loaded + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check that the BpfNsProgram's status was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth0.Status.Conditions[0].Type) + + // Check that the BpfNsProgram's status was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth1.Status.Conditions[0].Type) +} diff --git a/controllers/bpfman-agent/tcx-program.go b/controllers/bpfman-agent/tcx-program.go index 8df8661f2..0a6c4ed06 100644 --- a/controllers/bpfman-agent/tcx-program.go +++ b/controllers/bpfman-agent/tcx-program.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanagent import ( @@ -41,7 +43,7 @@ import ( // TcxProgramReconciler reconciles a tcxProgram object by creating multiple // bpfProgram objects and managing bpfman for each one. type TcxProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler currentTcxProgram *bpfmaniov1alpha1.TcxProgram interfaces []string ourNode *v1.Node @@ -71,6 +73,14 @@ func (r *TcxProgramReconciler) getName() string { return r.currentTcxProgram.Name } +func (r *TcxProgramReconciler) getNamespace() string { + return r.currentTcxProgram.Namespace +} + +func (r *TcxProgramReconciler) getNoContAnnotationIndex() string { + return internal.TcxNoContainersOnNode +} + func (r *TcxProgramReconciler) getNode() *v1.Node { return r.ourNode } @@ -149,7 +159,13 @@ func (r *TcxProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpf // There is a container selector, so see if there are any matching // containers on this node. - containerInfo, err := r.Containers.GetContainers(ctx, r.currentTcxProgram.Spec.Containers, r.Logger) + containerInfo, err := r.Containers.GetContainers( + ctx, + r.currentTcxProgram.Spec.Containers.Namespace, + r.currentTcxProgram.Spec.Containers.Pods, + r.currentTcxProgram.Spec.Containers.ContainerNames, + r.Logger, + ) if err != nil { return nil, fmt.Errorf("failed to get container pids: %v", err) } diff --git a/controllers/bpfman-agent/tcx-program_test.go b/controllers/bpfman-agent/tcx-program_test.go index 090d640de..7b343ff4d 100644 --- a/controllers/bpfman-agent/tcx-program_test.go +++ b/controllers/bpfman-agent/tcx-program_test.go @@ -24,7 +24,6 @@ import ( "google.golang.org/protobuf/testing/protocmp" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" testutils "github.com/bpfman/bpfman-operator/internal/test-utils" @@ -98,18 +97,21 @@ func TestTcxProgramControllerCreate(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &TcxProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &TcxProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -127,7 +129,7 @@ func TestTcxProgramControllerCreate(t *testing.T) { } // Check the BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.NotEmpty(t, bpfProg) @@ -178,11 +180,11 @@ func TestTcxProgramControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) // prog ID should already have been set - id, err := bpfmanagentinternal.GetID(bpfProg) + id, err := GetID(bpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly built @@ -201,7 +203,7 @@ func TestTcxProgramControllerCreate(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) @@ -265,18 +267,21 @@ func TestTcxProgramControllerCreateMultiIntf(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &TcxProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &TcxProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -294,7 +299,7 @@ func TestTcxProgramControllerCreateMultiIntf(t *testing.T) { } // Check the first BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) require.NotEmpty(t, bpfProgEth0) @@ -342,7 +347,7 @@ func TestTcxProgramControllerCreateMultiIntf(t *testing.T) { require.False(t, res.Requeue) // Check the Second BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) require.NotEmpty(t, bpfProgEth1) @@ -418,19 +423,19 @@ func TestTcxProgramControllerCreateMultiIntf(t *testing.T) { } // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) // prog ID should already have been set - id0, err := bpfmanagentinternal.GetID(bpfProgEth0) + id0, err := GetID(bpfProgEth0) require.NoError(t, err) // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) // prog ID should already have been set - id1, err := bpfmanagentinternal.GetID(bpfProgEth1) + id1, err := GetID(bpfProgEth1) require.NoError(t, err) // Check the bpfLoadRequest was correctly built @@ -446,11 +451,11 @@ func TestTcxProgramControllerCreateMultiIntf(t *testing.T) { } // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) // Third reconcile should update the bpfPrograms status to loaded @@ -463,13 +468,13 @@ func TestTcxProgramControllerCreateMultiIntf(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth0.Status.Conditions[0].Type) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth1.Status.Conditions[0].Type) diff --git a/controllers/bpfman-agent/test_common.go b/controllers/bpfman-agent/test_common.go index b6e795036..a0b9f48b0 100644 --- a/controllers/bpfman-agent/test_common.go +++ b/controllers/bpfman-agent/test_common.go @@ -17,8 +17,13 @@ type FakeContainerGetter struct { containerList *[]ContainerInfo } -func (f *FakeContainerGetter) GetContainers(ctx context.Context, containerSelector *bpfmaniov1alpha1.ContainerSelector, - logger logr.Logger) (*[]ContainerInfo, error) { +func (f *FakeContainerGetter) GetContainers( + ctx context.Context, + selectorNamespace string, + selectorPods metav1.LabelSelector, + selectorContainerNames *[]string, + logger logr.Logger, +) (*[]ContainerInfo, error) { return f.containerList, nil } @@ -64,7 +69,11 @@ func TestGetPods(t *testing.T) { require.NoError(t, err) // Call getPods and check the returned PodList - podList, err := containerGetter.getPodsForNode(ctx, containerSelector) + podList, err := containerGetter.getPodsForNode( + ctx, + containerSelector.Namespace, + containerSelector.Pods, + ) require.NoError(t, err) require.Len(t, podList.Items, 1) require.Equal(t, "test-pod", podList.Items[0].Name) @@ -77,7 +86,11 @@ func TestGetPods(t *testing.T) { }, } - podList, err = containerGetter.getPodsForNode(ctx, containerSelector) + podList, err = containerGetter.getPodsForNode( + ctx, + containerSelector.Namespace, + containerSelector.Pods, + ) require.NoError(t, err) require.Len(t, podList.Items, 1) require.Equal(t, "test-pod", podList.Items[0].Name) diff --git a/controllers/bpfman-agent/tracepoint-program.go b/controllers/bpfman-agent/tracepoint-program.go index 97480369c..7855399fd 100644 --- a/controllers/bpfman-agent/tracepoint-program.go +++ b/controllers/bpfman-agent/tracepoint-program.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanagent import ( @@ -39,7 +41,7 @@ import ( // BpfProgramReconciler reconciles a BpfProgram object type TracepointProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler ourNode *v1.Node currentTracepointProgram *bpfmaniov1alpha1.TracepointProgram } @@ -68,6 +70,14 @@ func (r *TracepointProgramReconciler) getName() string { return r.currentTracepointProgram.Name } +func (r *TracepointProgramReconciler) getNamespace() string { + return r.currentTracepointProgram.Namespace +} + +func (r *TracepointProgramReconciler) getNoContAnnotationIndex() string { + return internal.TracepointNoContainersOnNode +} + func (r *TracepointProgramReconciler) getNode() *v1.Node { return r.ourNode } diff --git a/controllers/bpfman-agent/tracepoint-program_test.go b/controllers/bpfman-agent/tracepoint-program_test.go index 3455f72a4..876fb5bf6 100644 --- a/controllers/bpfman-agent/tracepoint-program_test.go +++ b/controllers/bpfman-agent/tracepoint-program_test.go @@ -24,7 +24,6 @@ import ( "google.golang.org/protobuf/testing/protocmp" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" internal "github.com/bpfman/bpfman-operator/internal" testutils "github.com/bpfman/bpfman-operator/internal/test-utils" @@ -92,18 +91,21 @@ func TestTracepointProgramControllerCreate(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &TracepointProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &TracepointProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -121,7 +123,7 @@ func TestTracepointProgramControllerCreate(t *testing.T) { } // Check the BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.NotEmpty(t, bpfProg) @@ -170,11 +172,11 @@ func TestTracepointProgramControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) // prog ID should already have been set - id, err := bpfmanagentinternal.GetID(bpfProg) + id, err := GetID(bpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly Built @@ -184,7 +186,7 @@ func TestTracepointProgramControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) // Third reconcile should update the bpfPrograms status to loaded @@ -197,7 +199,7 @@ func TestTracepointProgramControllerCreate(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) diff --git a/controllers/bpfman-agent/uprobe-ns-program.go b/controllers/bpfman-agent/uprobe-ns-program.go new file mode 100644 index 000000000..a6ab77581 --- /dev/null +++ b/controllers/bpfman-agent/uprobe-ns-program.go @@ -0,0 +1,296 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanagent + +import ( + "context" + "fmt" + "strconv" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" + internal "github.com/bpfman/bpfman-operator/internal" + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=uprobensprograms,verbs=get;list;watch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=uprobensprograms,verbs=get;list;watch + +// BpfProgramReconciler reconciles a BpfNsProgram object +type UprobeNsProgramReconciler struct { + NamespaceProgramReconciler + currentUprobeNsProgram *bpfmaniov1alpha1.UprobeNsProgram + ourNode *v1.Node +} + +func (r *UprobeNsProgramReconciler) getFinalizer() string { + return r.finalizer +} + +func (r *UprobeNsProgramReconciler) getOwner() metav1.Object { + if r.appOwner == nil { + return r.currentUprobeNsProgram + } else { + return r.appOwner + } +} + +func (r *UprobeNsProgramReconciler) getRecType() string { + return r.recType +} + +func (r *UprobeNsProgramReconciler) getProgType() internal.ProgramType { + return internal.Kprobe +} + +func (r *UprobeNsProgramReconciler) getName() string { + return r.currentUprobeNsProgram.Name +} + +func (r *UprobeNsProgramReconciler) getNamespace() string { + return r.currentUprobeNsProgram.Namespace +} + +func (r *UprobeNsProgramReconciler) getNoContAnnotationIndex() string { + return internal.UprobeNsNoContainersOnNode +} + +func (r *UprobeNsProgramReconciler) getNode() *v1.Node { + return r.ourNode +} + +func (r *UprobeNsProgramReconciler) getBpfProgramCommon() *bpfmaniov1alpha1.BpfProgramCommon { + return &r.currentUprobeNsProgram.Spec.BpfProgramCommon +} + +func (r *UprobeNsProgramReconciler) getNodeSelector() *metav1.LabelSelector { + return &r.currentUprobeNsProgram.Spec.NodeSelector +} + +func (r *UprobeNsProgramReconciler) getBpfGlobalData() map[string][]byte { + return r.currentUprobeNsProgram.Spec.GlobalData +} + +func (r *UprobeNsProgramReconciler) getAppProgramId() string { + return appProgramId(r.currentUprobeNsProgram.GetLabels()) +} + +func (r *UprobeNsProgramReconciler) setCurrentProgram(program client.Object) error { + var ok bool + + r.currentUprobeNsProgram, ok = program.(*bpfmaniov1alpha1.UprobeNsProgram) + if !ok { + return fmt.Errorf("failed to cast program to UprobeNsProgram") + } + + return nil +} + +// SetupWithManager sets up the controller with the Manager. +// The Bpfman-Agent should reconcile whenever a UprobeNsProgram is updated, +// load the program to the node via bpfman, and then create a BpfNsProgram object +// to reflect per node state information. +func (r *UprobeNsProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.UprobeNsProgram{}, builder.WithPredicates(predicate.And(predicate.GenerationChangedPredicate{}, predicate.ResourceVersionChangedPredicate{}))). + Owns(&bpfmaniov1alpha1.BpfNsProgram{}, + builder.WithPredicates(predicate.And( + internal.BpfNsProgramTypePredicate(internal.UprobeString), + internal.BpfProgramNodePredicate(r.NodeName)), + ), + ). + // Trigger reconciliation if node labels change since that could make + // the UprobeNsProgram no longer select the Node. Trigger on pod events + // for when uprobes are attached inside containers. In both cases, only + // care about events specific to our node + Watches( + &v1.Node{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And(predicate.LabelChangedPredicate{}, nodePredicate(r.NodeName))), + ). + // Watch for changes in Pod resources in case we are using a container selector. + Watches( + &v1.Pod{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(podOnNodePredicate(r.NodeName)), + ). + Complete(r) +} + +func (r *UprobeNsProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfNsProgramList, error) { + progs := &bpfmaniov1alpha1.BpfNsProgramList{} + + sanitizedUprobe := sanitize(r.currentUprobeNsProgram.Spec.Target) + "-" + sanitize(r.currentUprobeNsProgram.Spec.FunctionName) + + // There is a container selector, so see if there are any matching + // containers on this node. + containerInfo, err := r.Containers.GetContainers( + ctx, + r.getNamespace(), + r.currentUprobeNsProgram.Spec.Containers.Pods, + r.currentUprobeNsProgram.Spec.Containers.ContainerNames, + r.Logger, + ) + if err != nil { + return nil, fmt.Errorf("failed to get container pids: %v", err) + } + if containerInfo == nil || len(*containerInfo) == 0 { + // There were no errors, but the container selector didn't + // select any containers on this node. + + annotations := map[string]string{ + internal.UprobeNsProgramTarget: r.currentUprobeNsProgram.Spec.Target, + internal.UprobeNsNoContainersOnNode: "true", + } + + attachPoint := sanitizedUprobe + "-no-containers-on-node" + + prog, err := r.createBpfProgram(attachPoint, r, annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfNsProgram %s: %v", attachPoint, err) + } + + progs.Items = append(progs.Items, *prog) + } else { + + // Containers were found, so create BpfNsPrograms. + for i := range *containerInfo { + container := (*containerInfo)[i] + + annotations := map[string]string{internal.UprobeNsProgramTarget: r.currentUprobeNsProgram.Spec.Target} + annotations[internal.UprobeNsContainerPid] = strconv.FormatInt(container.pid, 10) + + attachPoint := fmt.Sprintf("%s-%s-%s", + sanitizedUprobe, + container.podName, + container.containerName, + ) + + prog, err := r.createBpfProgram(attachPoint, r, annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfNsProgram %s: %v", attachPoint, err) + } + + progs.Items = append(progs.Items, *prog) + } + } + + return progs, nil +} + +func (r *UprobeNsProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // Initialize node and current program + r.currentUprobeNsProgram = &bpfmaniov1alpha1.UprobeNsProgram{} + r.finalizer = internal.UprobeNsProgramControllerFinalizer + r.recType = internal.UprobeString + r.ourNode = &v1.Node{} + r.Logger = ctrl.Log.WithName("uprobe-ns") + + r.Logger.Info("bpfman-agent enter: uprobe-ns", "Namespace", req.Namespace, "Name", req.Name) + + // Lookup K8s node object for this bpfman-agent This should always succeed + if err := r.Get(ctx, types.NamespacedName{Namespace: v1.NamespaceAll, Name: r.NodeName}, r.ourNode); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfman-agent node %s : %v", + req.NamespacedName, err) + } + + uprobePrograms := &bpfmaniov1alpha1.UprobeNsProgramList{} + + opts := []client.ListOption{} + + if err := r.List(ctx, uprobePrograms, opts...); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting UprobeNsPrograms for full reconcile %s : %v", + req.NamespacedName, err) + } + + if len(uprobePrograms.Items) == 0 { + r.Logger.Info("UprobeNsProgramController found no Uprobe Programs") + return ctrl.Result{Requeue: false}, nil + } + + // Create a list of Uprobe programs to pass into reconcileCommon() + var uprobeObjects []client.Object = make([]client.Object, len(uprobePrograms.Items)) + for i := range uprobePrograms.Items { + uprobeObjects[i] = &uprobePrograms.Items[i] + } + + // Reconcile each TcProgram. + _, result, err := r.reconcileCommon(ctx, r, uprobeObjects) + return result, err +} + +func (r *UprobeNsProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfNsProgram, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) { + bytecode, err := bpfmanagentinternal.GetBytecode(r.Client, &r.currentUprobeNsProgram.Spec.ByteCode) + if err != nil { + return nil, fmt.Errorf("failed to process bytecode selector: %v", err) + } + + var uprobeAttachInfo *gobpfman.UprobeAttachInfo + + var containerPid int32 + hasContainerPid := false + + containerPidStr, ok := bpfProgram.Annotations[internal.UprobeNsContainerPid] + + if ok { + containerPidInt64, err := strconv.ParseInt(containerPidStr, 10, 32) + if err != nil { + r.Logger.Error(err, "ParseInt() error on containerPidStr", "containerPidStr", containerPidStr) + } else { + containerPid = int32(containerPidInt64) + hasContainerPid = true + } + } + + uprobeAttachInfo = &gobpfman.UprobeAttachInfo{ + FnName: &r.currentUprobeNsProgram.Spec.FunctionName, + Offset: r.currentUprobeNsProgram.Spec.Offset, + Target: bpfProgram.Annotations[internal.UprobeNsProgramTarget], + Retprobe: r.currentUprobeNsProgram.Spec.RetProbe, + } + + if hasContainerPid { + uprobeAttachInfo.ContainerPid = &containerPid + } + + loadRequest := gobpfman.LoadRequest{ + Bytecode: bytecode, + Name: r.currentUprobeNsProgram.Spec.BpfFunctionName, + ProgramType: uint32(internal.Kprobe), + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_UprobeAttachInfo{ + UprobeAttachInfo: uprobeAttachInfo, + }, + }, + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.getOwner().GetName()}, + GlobalData: r.currentUprobeNsProgram.Spec.GlobalData, + MapOwnerId: mapOwnerId, + } + + return &loadRequest, nil +} diff --git a/controllers/bpfman-agent/uprobe-ns-program_test.go b/controllers/bpfman-agent/uprobe-ns-program_test.go new file mode 100644 index 000000000..4af32f64b --- /dev/null +++ b/controllers/bpfman-agent/uprobe-ns-program_test.go @@ -0,0 +1,243 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanagent + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" + internal "github.com/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestUprobeNsProgramControllerCreate(t *testing.T) { + var ( + name = "fakeUprobeProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + functionName = "malloc" + target = "libc" + offset = 0 + retprobe = false + fakeNode = testutils.NewNode("fake-control-plane") + fakePodName = "my-pod" + fakeContainerName = "my-container-1" + fakePid = int64(8712) + ctx = context.TODO() + appProgramId = "" + bpfProg = &bpfmaniov1alpha1.BpfNsProgram{} + fakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81" + attachPoint = fmt.Sprintf("%s-%s-%s-%s", + sanitize(target), + sanitize(functionName), + fakePodName, + fakeContainerName, + ) + ) + // A UprobeProgram object with metadata and spec. + Uprobe := &bpfmaniov1alpha1.UprobeNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: bpfmaniov1alpha1.UprobeNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + UprobeNsProgramInfo: bpfmaniov1alpha1.UprobeNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + FunctionName: functionName, + Target: target, + Offset: uint64(offset), + RetProbe: retprobe, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": fakePodName, + }, + }, + }, + }, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, Uprobe} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, Uprobe) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.UprobeNsProgramList{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(Uprobe).WithStatusSubresource(&bpfmaniov1alpha1.BpfNsProgram{}).WithRuntimeObjects(objs...).Build() + + cli := agenttestutils.NewBpfmanClientFake() + + testContainers := FakeContainerGetter{ + containerList: &[]ContainerInfo{ + { + podName: fakePodName, + containerName: fakeContainerName, + pid: fakePid, + }, + }, + } + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + BpfmanClient: cli, + NodeName: fakeNode.Name, + Containers: &testContainers, + } + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &UprobeNsProgramReconciler{NamespaceProgramReconciler: npr, ourNode: fakeNode} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should create the bpf program object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Check the BpfProgram Object was created successfully + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) + require.NoError(t, err) + + require.NotEmpty(t, bpfProg) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProg.Finalizers[0]) + // owningConfig Label was correctly set + require.Equal(t, bpfProg.Labels[internal.BpfProgramOwner], name) + // node Label was correctly set + require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name) + // uprobe function Annotation was correctly set + require.Equal(t, bpfProg.Annotations[internal.UprobeNsProgramTarget], target) + // Type is set + require.Equal(t, r.getRecType(), bpfProg.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of bpfProgram with Fake UID since the fake API server won't + bpfProg.UID = types.UID(fakeUID) + err = cl.Update(ctx, bpfProg) + require.NoError(t, err) + + // Second reconcile should create the bpfman Load Request and update the + // BpfProgram object's maps field and id annotation. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + pid32 := int32(fakePid) + + // Require no requeue + require.False(t, res.Requeue) + expectedLoadReq := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Kprobe.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProg.UID), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_UprobeAttachInfo{ + UprobeAttachInfo: &gobpfman.UprobeAttachInfo{ + FnName: &functionName, + Target: target, + Offset: uint64(offset), + Retprobe: retprobe, + ContainerPid: &pid32, + }, + }, + }, + } + + // Check that the bpfProgram's programs was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) + require.NoError(t, err) + + // prog ID should already have been set + id, err := GetID(bpfProg) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly Built + if !cmp.Equal(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) { + cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) + t.Logf("Diff %v", cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Third reconcile should set the status to loaded + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check that the bpfProgram's status was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) +} diff --git a/controllers/bpfman-agent/uprobe-program.go b/controllers/bpfman-agent/uprobe-program.go index 72f90ed31..20875729f 100644 --- a/controllers/bpfman-agent/uprobe-program.go +++ b/controllers/bpfman-agent/uprobe-program.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanagent import ( @@ -40,7 +42,7 @@ import ( // BpfProgramReconciler reconciles a BpfProgram object type UprobeProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler currentUprobeProgram *bpfmaniov1alpha1.UprobeProgram ourNode *v1.Node } @@ -69,6 +71,14 @@ func (r *UprobeProgramReconciler) getName() string { return r.currentUprobeProgram.Name } +func (r *UprobeProgramReconciler) getNamespace() string { + return r.currentUprobeProgram.Namespace +} + +func (r *UprobeProgramReconciler) getNoContAnnotationIndex() string { + return internal.UprobeNoContainersOnNode +} + func (r *UprobeProgramReconciler) getNode() *v1.Node { return r.ourNode } @@ -140,7 +150,13 @@ func (r *UprobeProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (* // There is a container selector, so see if there are any matching // containers on this node. - containerInfo, err := r.Containers.GetContainers(ctx, r.currentUprobeProgram.Spec.Containers, r.Logger) + containerInfo, err := r.Containers.GetContainers( + ctx, + r.currentUprobeProgram.Spec.Containers.Namespace, + r.currentUprobeProgram.Spec.Containers.Pods, + r.currentUprobeProgram.Spec.Containers.ContainerNames, + r.Logger, + ) if err != nil { return nil, fmt.Errorf("failed to get container pids: %v", err) } diff --git a/controllers/bpfman-agent/uprobe-program_test.go b/controllers/bpfman-agent/uprobe-program_test.go index a22d367d2..36abeaa15 100644 --- a/controllers/bpfman-agent/uprobe-program_test.go +++ b/controllers/bpfman-agent/uprobe-program_test.go @@ -25,7 +25,6 @@ import ( "google.golang.org/protobuf/testing/protocmp" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" internal "github.com/bpfman/bpfman-operator/internal" testutils "github.com/bpfman/bpfman-operator/internal/test-utils" @@ -99,18 +98,21 @@ func TestUprobeProgramControllerCreate(t *testing.T) { cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &UprobeProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &UprobeProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -128,7 +130,7 @@ func TestUprobeProgramControllerCreate(t *testing.T) { } // Check the BpfProgram Object was created successfully - err = r.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.NotEmpty(t, bpfProg) @@ -180,11 +182,11 @@ func TestUprobeProgramControllerCreate(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = r.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) // prog ID should already have been set - id, err := bpfmanagentinternal.GetID(bpfProg) + id, err := GetID(bpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly Built @@ -204,7 +206,7 @@ func TestUprobeProgramControllerCreate(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = r.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) @@ -290,7 +292,7 @@ func TestUprobeProgramControllerCreateContainer(t *testing.T) { }, } - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, @@ -298,11 +300,15 @@ func TestUprobeProgramControllerCreateContainer(t *testing.T) { Containers: &testContainers, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &UprobeProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &UprobeProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -320,7 +326,7 @@ func TestUprobeProgramControllerCreateContainer(t *testing.T) { } // Check the BpfProgram Object was created successfully - err = r.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.NotEmpty(t, bpfProg) @@ -375,11 +381,11 @@ func TestUprobeProgramControllerCreateContainer(t *testing.T) { } // Check that the bpfProgram's programs was correctly updated - err = r.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) // prog ID should already have been set - id, err := bpfmanagentinternal.GetID(bpfProg) + id, err := GetID(bpfProg) require.NoError(t, err) // Check the bpfLoadRequest was correctly Built @@ -399,7 +405,7 @@ func TestUprobeProgramControllerCreateContainer(t *testing.T) { require.False(t, res.Requeue) // Check that the bpfProgram's status was correctly updated - err = r.getBpfProgram(ctx, name, appProgramId, attachPoint, bpfProg) + err = r.getBpfProgram(ctx, r, name, appProgramId, attachPoint, bpfProg) require.NoError(t, err) require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) diff --git a/controllers/bpfman-agent/xdp-ns-program.go b/controllers/bpfman-agent/xdp-ns-program.go new file mode 100644 index 000000000..e87551909 --- /dev/null +++ b/controllers/bpfman-agent/xdp-ns-program.go @@ -0,0 +1,295 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanagent + +import ( + "context" + "fmt" + "strconv" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" + internal "github.com/bpfman/bpfman-operator/internal" + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=xdpnsprograms,verbs=get;list;watch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=xdpnsprograms,verbs=get;list;watch + +// BpfProgramReconciler reconciles a BpfNsProgram object +type XdpNsProgramReconciler struct { + NamespaceProgramReconciler + currentXdpNsProgram *bpfmaniov1alpha1.XdpNsProgram + interfaces []string + ourNode *v1.Node +} + +func (r *XdpNsProgramReconciler) getFinalizer() string { + return r.finalizer +} + +func (r *XdpNsProgramReconciler) getOwner() metav1.Object { + if r.appOwner == nil { + return r.currentXdpNsProgram + } else { + return r.appOwner + } +} + +func (r *XdpNsProgramReconciler) getRecType() string { + return r.recType +} + +func (r *XdpNsProgramReconciler) getProgType() internal.ProgramType { + return internal.Xdp +} + +func (r *XdpNsProgramReconciler) getName() string { + return r.currentXdpNsProgram.Name +} + +func (r *XdpNsProgramReconciler) getNamespace() string { + return r.currentXdpNsProgram.Namespace +} + +func (r *XdpNsProgramReconciler) getNoContAnnotationIndex() string { + return internal.XdpNsNoContainersOnNode +} + +func (r *XdpNsProgramReconciler) getNode() *v1.Node { + return r.ourNode +} + +func (r *XdpNsProgramReconciler) getBpfProgramCommon() *bpfmaniov1alpha1.BpfProgramCommon { + return &r.currentXdpNsProgram.Spec.BpfProgramCommon +} + +func (r *XdpNsProgramReconciler) getNodeSelector() *metav1.LabelSelector { + return &r.currentXdpNsProgram.Spec.NodeSelector +} + +func (r *XdpNsProgramReconciler) getBpfGlobalData() map[string][]byte { + return r.currentXdpNsProgram.Spec.GlobalData +} + +func (r *XdpNsProgramReconciler) getAppProgramId() string { + return appProgramId(r.currentXdpNsProgram.GetLabels()) +} + +func (r *XdpNsProgramReconciler) setCurrentProgram(program client.Object) error { + var err error + var ok bool + + r.currentXdpNsProgram, ok = program.(*bpfmaniov1alpha1.XdpNsProgram) + if !ok { + return fmt.Errorf("failed to cast program to XdpNsProgram") + } + + r.interfaces, err = getInterfaces(&r.currentXdpNsProgram.Spec.InterfaceSelector, r.ourNode) + if err != nil { + return fmt.Errorf("failed to get interfaces for XdpNsProgram: %v", err) + } + + return nil +} + +// SetupWithManager sets up the controller with the Manager. +// The Bpfman-Agent should reconcile whenever a XdpNsProgram is updated, +// load the program to the node via bpfman, and then create a bpfProgram object +// to reflect per node state information. +func (r *XdpNsProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.XdpNsProgram{}, + builder.WithPredicates(predicate.And( + predicate.GenerationChangedPredicate{}, + predicate.ResourceVersionChangedPredicate{}), + ), + ). + Owns(&bpfmaniov1alpha1.BpfNsProgram{}, + builder.WithPredicates(predicate.And( + internal.BpfNsProgramTypePredicate(internal.Xdp.String()), + internal.BpfProgramNodePredicate(r.NodeName)), + ), + ). + // Only trigger reconciliation if node labels change since that could + // make the XdpNsProgram no longer select the Node. Additionally only + // care about node events specific to our node + Watches( + &v1.Node{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And(predicate.LabelChangedPredicate{}, nodePredicate(r.NodeName))), + ). + // Watch for changes in Pod resources in case we are using a container selector. + Watches( + &v1.Pod{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(podOnNodePredicate(r.NodeName)), + ). + Complete(r) +} + +func (r *XdpNsProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfNsProgramList, error) { + progs := &bpfmaniov1alpha1.BpfNsProgramList{} + + // There is a container selector, so see if there are any matching + // containers on this node. + containerInfo, err := r.Containers.GetContainers( + ctx, + r.getNamespace(), + r.currentXdpNsProgram.Spec.Containers.Pods, + r.currentXdpNsProgram.Spec.Containers.ContainerNames, + r.Logger, + ) + if err != nil { + return nil, fmt.Errorf("failed to get container pids: %v", err) + } + + if containerInfo == nil || len(*containerInfo) == 0 { + // There were no errors, but the container selector didn't + // select any containers on this node. + for _, iface := range r.interfaces { + attachPoint := fmt.Sprintf("%s-%s", + iface, + "no-containers-on-node", + ) + + annotations := map[string]string{ + internal.XdpNsProgramInterface: iface, + internal.XdpNsNoContainersOnNode: "true", + } + + prog, err := r.createBpfProgram(attachPoint, r, annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfProgram %s: %v", attachPoint, err) + } + + progs.Items = append(progs.Items, *prog) + } + } else { + // Containers were found, so create bpfPrograms. + for i := range *containerInfo { + container := (*containerInfo)[i] + for _, iface := range r.interfaces { + attachPoint := fmt.Sprintf("%s-%s-%s", + iface, + container.podName, + container.containerName, + ) + + annotations := map[string]string{ + internal.XdpNsProgramInterface: iface, + internal.XdpNsContainerPid: strconv.FormatInt(container.pid, 10), + } + + prog, err := r.createBpfProgram(attachPoint, r, annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfProgram %s: %v", attachPoint, err) + } + + progs.Items = append(progs.Items, *prog) + } + } + } + + return progs, nil +} + +func (r *XdpNsProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // Initialize node and current program + r.currentXdpNsProgram = &bpfmaniov1alpha1.XdpNsProgram{} + r.finalizer = internal.XdpNsProgramControllerFinalizer + r.recType = internal.Xdp.String() + r.ourNode = &v1.Node{} + r.Logger = ctrl.Log.WithName("xdp-ns") + + r.Logger.Info("bpfman-agent enter: xdp-ns", "Namespace", req.Namespace, "Name", req.Name) + + // Lookup K8s node object for this bpfman-agent This should always succeed + if err := r.Get(ctx, types.NamespacedName{Namespace: v1.NamespaceAll, Name: r.NodeName}, r.ourNode); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfman-agent node %s : %v", + req.NamespacedName, err) + } + + xdpPrograms := &bpfmaniov1alpha1.XdpNsProgramList{} + opts := []client.ListOption{} + + if err := r.List(ctx, xdpPrograms, opts...); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting XdpNsPrograms for full reconcile %s : %v", + req.NamespacedName, err) + } + + if len(xdpPrograms.Items) == 0 { + r.Logger.Info("XdpNsProgramController found no XDP Programs") + return ctrl.Result{Requeue: false}, nil + } + + // Create a list of Xdp programs to pass into reconcileCommon() + var xdpObjects []client.Object = make([]client.Object, len(xdpPrograms.Items)) + for i := range xdpPrograms.Items { + xdpObjects[i] = &xdpPrograms.Items[i] + } + + // Reconcile each TcProgram. + _, result, err := r.reconcileCommon(ctx, r, xdpObjects) + return result, err +} + +func (r *XdpNsProgramReconciler) getLoadRequest(bpfProgram *bpfmaniov1alpha1.BpfNsProgram, mapOwnerId *uint32) (*gobpfman.LoadRequest, error) { + bytecode, err := bpfmanagentinternal.GetBytecode(r.Client, &r.currentXdpNsProgram.Spec.ByteCode) + if err != nil { + return nil, fmt.Errorf("failed to process bytecode selector: %v", err) + } + + attachInfo := &gobpfman.XDPAttachInfo{ + Priority: r.currentXdpNsProgram.Spec.Priority, + Iface: bpfProgram.Annotations[internal.XdpNsProgramInterface], + ProceedOn: xdpProceedOnToInt(r.currentXdpNsProgram.Spec.ProceedOn), + } + + containerPidStr, ok := bpfProgram.Annotations[internal.XdpNsContainerPid] + if ok { + netns := fmt.Sprintf("/host/proc/%s/ns/net", containerPidStr) + attachInfo.Netns = &netns + } + + loadRequest := gobpfman.LoadRequest{ + Bytecode: bytecode, + Name: r.currentXdpNsProgram.Spec.BpfFunctionName, + ProgramType: uint32(internal.Xdp), + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_XdpAttachInfo{ + XdpAttachInfo: attachInfo, + }, + }, + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProgram.UID), internal.ProgramNameKey: r.getOwner().GetName()}, + GlobalData: r.currentXdpNsProgram.Spec.GlobalData, + MapOwnerId: mapOwnerId, + } + + return &loadRequest, nil +} diff --git a/controllers/bpfman-agent/xdp-ns-program_test.go b/controllers/bpfman-agent/xdp-ns-program_test.go new file mode 100644 index 000000000..316f86318 --- /dev/null +++ b/controllers/bpfman-agent/xdp-ns-program_test.go @@ -0,0 +1,390 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanagent + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" + internal "github.com/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +// Runs the XdpProgramControllerCreate test. If multiInterface = true, it +// installs the program on two interfaces. If multiCondition == true, it runs +// it with an error case in which the program object has multiple conditions. +func xdpNsProgramControllerCreate(t *testing.T, multiInterface bool, multiCondition bool) { + var ( + name = "fakeXdpProgram" + namespace = "my-namespace" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + fakeNode = testutils.NewNode("fake-control-plane") + fakePodName = "my-pod" + fakeContainerName = "my-container-1" + fakePid = int64(12201) + fakeInt0 = "eth0" + fakeInt1 = "eth1" + ctx = context.TODO() + appProgramId0 = "" + appProgramId1 = "" + bpfProgEth0 = &bpfmaniov1alpha1.BpfNsProgram{} + bpfProgEth1 = &bpfmaniov1alpha1.BpfNsProgram{} + fakeUID0 = "ef71d42c-aa21-48e8-a697-82391d801a80" + fakeUID1 = "ef71d42c-aa21-48e8-a697-82391d801a81" + attachPoint0 = fmt.Sprintf("%s-%s-%s", + fakeInt0, + fakePodName, + fakeContainerName, + ) + attachPoint1 = fmt.Sprintf("%s-%s-%s", + fakeInt1, + fakePodName, + fakeContainerName, + ) + ) + + var fakeInts []string + if multiInterface { + fakeInts = []string{fakeInt0, fakeInt1} + } else { + fakeInts = []string{fakeInt0} + } + + // A XdpNsProgram object with metadata and spec. + xdp := &bpfmaniov1alpha1.XdpNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: bpfmaniov1alpha1.XdpNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + XdpNsProgramInfo: bpfmaniov1alpha1.XdpNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &fakeInts, + }, + Priority: 0, + ProceedOn: []bpfmaniov1alpha1.XdpProceedOnValue{bpfmaniov1alpha1.XdpProceedOnValue("pass"), + bpfmaniov1alpha1.XdpProceedOnValue("dispatcher_return"), + }, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": fakePodName, + }, + }, + }, + }, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, xdp} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, xdp) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.XdpNsProgramList{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(xdp).WithStatusSubresource(&bpfmaniov1alpha1.BpfNsProgram{}).WithRuntimeObjects(objs...).Build() + + cli := agenttestutils.NewBpfmanClientFake() + + testContainers := FakeContainerGetter{ + containerList: &[]ContainerInfo{ + { + podName: fakePodName, + containerName: fakeContainerName, + pid: fakePid, + }, + }, + } + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + BpfmanClient: cli, + NodeName: fakeNode.Name, + Containers: &testContainers, + } + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &XdpNsProgramReconciler{NamespaceProgramReconciler: npr, ourNode: fakeNode} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should create the first bpf program object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Check the first BpfProgram Object was created successfully + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + require.NotEmpty(t, bpfProgEth0) + // owningConfig Label was correctly set + require.Equal(t, bpfProgEth0.Labels[internal.BpfProgramOwner], name) + // node Label was correctly set + require.Equal(t, bpfProgEth0.Labels[internal.K8sHostLabel], fakeNode.Name) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProgEth0.Finalizers[0]) + // Type is set + require.Equal(t, r.getRecType(), bpfProgEth0.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of bpfProgram with Fake UID since the fake API server won't + bpfProgEth0.UID = types.UID(fakeUID0) + err = cl.Update(ctx, bpfProgEth0) + require.NoError(t, err) + + // Second reconcile should create the bpfman Load Requests for the first bpfProgram. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + uuid0 := string(bpfProgEth0.UID) + netns := fmt.Sprintf("/host/proc/%d/ns/net", fakePid) + + expectedLoadReq0 := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Xdp.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: uuid0, internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_XdpAttachInfo{ + XdpAttachInfo: &gobpfman.XDPAttachInfo{ + Priority: 0, + Iface: fakeInts[0], + ProceedOn: []int32{2, 31}, + Netns: &netns, + }, + }, + }, + } + + // Check that the bpfProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + // prog ID should already have been set + id0, err := GetID(bpfProgEth0) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly built + if !cmp.Equal(expectedLoadReq0, cli.LoadRequests[int(*id0)], protocmp.Transform()) { + t.Logf("Diff %v", cmp.Diff(expectedLoadReq0, cli.LoadRequests[int(*id0)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // NOTE: THIS IS A TEST FOR AN ERROR PATH. THERE SHOULD NEVER BE MORE THAN + // ONE CONDITION. + if multiCondition { + // Add some random conditions and verify that the condition still gets + // updated correctly. + meta.SetStatusCondition(&bpfProgEth0.Status.Conditions, bpfmaniov1alpha1.BpfProgCondBytecodeSelectorError.Condition()) + if err := r.Status().Update(ctx, bpfProgEth0); err != nil { + r.Logger.V(1).Info("failed to set KprobeProgram object status") + } + meta.SetStatusCondition(&bpfProgEth0.Status.Conditions, bpfmaniov1alpha1.BpfProgCondNotSelected.Condition()) + if err := r.Status().Update(ctx, bpfProgEth0); err != nil { + r.Logger.V(1).Info("failed to set KprobeProgram object status") + } + // Make sure we have 2 conditions + require.Equal(t, 2, len(bpfProgEth0.Status.Conditions)) + } + + // Third reconcile should update the bpfPrograms status to loaded + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Get program object + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) + require.NoError(t, err) + + // Check that the bpfProgram's status was correctly updated + // Make sure we only have 1 condition now + require.Equal(t, 1, len(bpfProgEth0.Status.Conditions)) + // Make sure it's the right one. + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth0.Status.Conditions[0].Type) + + if multiInterface { + // Fourth reconcile should create the second bpfProgram. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the Second BpfProgram Object was created successfully + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + require.NotEmpty(t, bpfProgEth1) + // owningConfig Label was correctly set + require.Equal(t, bpfProgEth1.Labels[internal.BpfProgramOwner], name) + // node Label was correctly set + require.Equal(t, bpfProgEth1.Labels[internal.K8sHostLabel], fakeNode.Name) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProgEth1.Finalizers[0]) + // Type is set + require.Equal(t, r.getRecType(), bpfProgEth1.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of bpfProgram with Fake UID since the fake API server won't + bpfProgEth1.UID = types.UID(fakeUID1) + err = cl.Update(ctx, bpfProgEth1) + require.NoError(t, err) + + // Fifth reconcile should create the second bpfProgram. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Sixth reconcile should update the second bpfProgram's status. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + uuid1 := string(bpfProgEth1.UID) + netns := fmt.Sprintf("/host/proc/%d/ns/net", fakePid) + + expectedLoadReq1 := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Xdp.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: uuid1, internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_XdpAttachInfo{ + XdpAttachInfo: &gobpfman.XDPAttachInfo{ + Priority: 0, + Iface: fakeInts[1], + ProceedOn: []int32{2, 31}, + Netns: &netns, + }, + }, + }, + } + + // Check that the bpfProgram's maps was correctly updated + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + // prog ID should already have been set + id1, err := GetID(bpfProgEth1) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly built + if !cmp.Equal(expectedLoadReq1, cli.LoadRequests[int(*id1)], protocmp.Transform()) { + t.Logf("Diff %v", cmp.Diff(expectedLoadReq1, cli.LoadRequests[int(*id1)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Get program object + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) + require.NoError(t, err) + + // Check that the bpfProgram's status was correctly updated + // Make sure we only have 1 condition now + require.Equal(t, 1, len(bpfProgEth1.Status.Conditions)) + // Make sure it's the right one. + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProgEth1.Status.Conditions[0].Type) + } +} + +func TestXdpNsProgramControllerCreate(t *testing.T) { + xdpNsProgramControllerCreate(t, false, false) +} + +func TestXdpNsProgramControllerCreateMultiIntf(t *testing.T) { + xdpNsProgramControllerCreate(t, true, false) +} + +func TestXdpNsUpdateStatus(t *testing.T) { + xdpNsProgramControllerCreate(t, false, true) +} diff --git a/controllers/bpfman-agent/xdp-program.go b/controllers/bpfman-agent/xdp-program.go index 364217908..07648ac7c 100644 --- a/controllers/bpfman-agent/xdp-program.go +++ b/controllers/bpfman-agent/xdp-program.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanagent import ( @@ -40,7 +42,7 @@ import ( // BpfProgramReconciler reconciles a BpfProgram object type XdpProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler currentXdpProgram *bpfmaniov1alpha1.XdpProgram interfaces []string ourNode *v1.Node @@ -70,6 +72,14 @@ func (r *XdpProgramReconciler) getName() string { return r.currentXdpProgram.Name } +func (r *XdpProgramReconciler) getNamespace() string { + return r.currentXdpProgram.Namespace +} + +func (r *XdpProgramReconciler) getNoContAnnotationIndex() string { + return internal.XdpNoContainersOnNode +} + func (r *XdpProgramReconciler) getNode() *v1.Node { return r.ourNode } @@ -168,7 +178,13 @@ func (r *XdpProgramReconciler) getExpectedBpfPrograms(ctx context.Context) (*bpf // There is a container selector, so see if there are any matching // containers on this node. - containerInfo, err := r.Containers.GetContainers(ctx, r.currentXdpProgram.Spec.Containers, r.Logger) + containerInfo, err := r.Containers.GetContainers( + ctx, + r.currentXdpProgram.Spec.Containers.Namespace, + r.currentXdpProgram.Spec.Containers.Pods, + r.currentXdpProgram.Spec.Containers.ContainerNames, + r.Logger, + ) if err != nil { return nil, fmt.Errorf("failed to get container pids: %v", err) } @@ -244,7 +260,7 @@ func (r *XdpProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) r.ourNode = &v1.Node{} r.Logger = ctrl.Log.WithName("xdp") - r.Logger.Info("bpfman-agent enter: XDP", "Name", req.Name) + r.Logger.Info("bpfman-agent enter: xdp", "Name", req.Name) // Lookup K8s node object for this bpfman-agent This should always succeed if err := r.Get(ctx, types.NamespacedName{Namespace: v1.NamespaceAll, Name: r.NodeName}, r.ourNode); err != nil { diff --git a/controllers/bpfman-agent/xdp-program_test.go b/controllers/bpfman-agent/xdp-program_test.go index 922cd28e4..e6378758c 100644 --- a/controllers/bpfman-agent/xdp-program_test.go +++ b/controllers/bpfman-agent/xdp-program_test.go @@ -24,7 +24,6 @@ import ( "google.golang.org/protobuf/testing/protocmp" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal" agenttestutils "github.com/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" internal "github.com/bpfman/bpfman-operator/internal" testutils "github.com/bpfman/bpfman-operator/internal/test-utils" @@ -115,18 +114,21 @@ func xdpProgramControllerCreate(t *testing.T, multiInterface bool, multiConditio cli := agenttestutils.NewBpfmanClientFake() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, BpfmanClient: cli, NodeName: fakeNode.Name, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &XdpProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + r := &XdpProgramReconciler{ClusterProgramReconciler: cpr, ourNode: fakeNode} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -144,7 +146,7 @@ func xdpProgramControllerCreate(t *testing.T, multiInterface bool, multiConditio } // Check the first BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) require.NotEmpty(t, bpfProgEth0) @@ -195,11 +197,11 @@ func xdpProgramControllerCreate(t *testing.T, multiInterface bool, multiConditio } // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) // prog ID should already have been set - id0, err := bpfmanagentinternal.GetID(bpfProgEth0) + id0, err := GetID(bpfProgEth0) require.NoError(t, err) // Check the bpfLoadRequest was correctly built @@ -235,7 +237,7 @@ func xdpProgramControllerCreate(t *testing.T, multiInterface bool, multiConditio require.False(t, res.Requeue) // Get program object - err = rc.getBpfProgram(ctx, name, appProgramId0, attachPoint0, bpfProgEth0) + err = r.getBpfProgram(ctx, r, name, appProgramId0, attachPoint0, bpfProgEth0) require.NoError(t, err) // Check that the bpfProgram's status was correctly updated @@ -255,7 +257,7 @@ func xdpProgramControllerCreate(t *testing.T, multiInterface bool, multiConditio require.False(t, res.Requeue) // Check the Second BpfProgram Object was created successfully - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) require.NotEmpty(t, bpfProgEth1) @@ -315,11 +317,11 @@ func xdpProgramControllerCreate(t *testing.T, multiInterface bool, multiConditio } // Check that the bpfProgram's maps was correctly updated - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) // prog ID should already have been set - id1, err := bpfmanagentinternal.GetID(bpfProgEth1) + id1, err := GetID(bpfProgEth1) require.NoError(t, err) // Check the bpfLoadRequest was correctly built @@ -329,7 +331,7 @@ func xdpProgramControllerCreate(t *testing.T, multiInterface bool, multiConditio } // Get program object - err = rc.getBpfProgram(ctx, name, appProgramId1, attachPoint1, bpfProgEth1) + err = r.getBpfProgram(ctx, r, name, appProgramId1, attachPoint1, bpfProgEth1) require.NoError(t, err) // Check that the bpfProgram's status was correctly updated diff --git a/controllers/bpfman-operator/application-ns-program_test.go b/controllers/bpfman-operator/application-ns-program_test.go new file mode 100644 index 000000000..770064549 --- /dev/null +++ b/controllers/bpfman-operator/application-ns-program_test.go @@ -0,0 +1,256 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanoperator + +import ( + "context" + "fmt" + "testing" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + internal "github.com/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + + "github.com/stretchr/testify/require" + meta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +// Runs the ApplicationProgramReconcile test. If multiCondition == true, it runs it +// with an error case in which the program object has multiple conditions. +func appNsProgramReconcile(t *testing.T, multiCondition bool) { + var ( + name = "fakeAppProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + bpfTcFunctionName = "tc_test" + bpfUprobeFunctionName = "uprobe_test" + bpfXdpFunctionName = "xdp-test" + fakeNode = testutils.NewNode("fake-control-plane") + tcFakeInt = "eth0" + tcDirection = "ingress" + uprobeFunctionName = "malloc" + uprobeTarget = "libc" + uprobeOffset = 0 + uprobeRetprobe = false + xdpFakeInt = "eth0" + ctx = context.TODO() + bpfProgName = fmt.Sprintf("%s-%s", name, fakeNode.Name) + ) + // A AppProgram object with metadata and spec. + App := &bpfmaniov1alpha1.BpfNsApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: bpfmaniov1alpha1.BpfNsApplicationSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + Programs: []bpfmaniov1alpha1.BpfNsApplicationProgram{ + { + Type: bpfmaniov1alpha1.ProgTypeTC, + TC: &bpfmaniov1alpha1.TcNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfTcFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &[]string{tcFakeInt}, + }, + Priority: 0, + Direction: tcDirection, + ProceedOn: []bpfmaniov1alpha1.TcProceedOnValue{ + bpfmaniov1alpha1.TcProceedOnValue("pipe"), + bpfmaniov1alpha1.TcProceedOnValue("dispatcher_return"), + }, + }, + }, + { + Type: bpfmaniov1alpha1.ProgTypeUprobe, + Uprobe: &bpfmaniov1alpha1.UprobeNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfUprobeFunctionName, + }, + FunctionName: uprobeFunctionName, + Target: uprobeTarget, + Offset: uint64(uprobeOffset), + RetProbe: uprobeRetprobe, + }, + /* + Containers: &bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + */ + }, + { + Type: bpfmaniov1alpha1.ProgTypeXDP, + XDP: &bpfmaniov1alpha1.XdpNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfXdpFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &[]string{xdpFakeInt}, + }, + Priority: 0, + ProceedOn: []bpfmaniov1alpha1.XdpProceedOnValue{bpfmaniov1alpha1.XdpProceedOnValue("pass"), + bpfmaniov1alpha1.XdpProceedOnValue("dispatcher_return"), + }, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + }, + }, + }, + }, + } + + // The expected accompanying BpfNsProgram object + expectedBpfProg := &bpfmaniov1alpha1.BpfNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: bpfProgName, + Namespace: namespace, + OwnerReferences: []metav1.OwnerReference{ + { + Name: App.Name, + Controller: &[]bool{true}[0], + }, + }, + Labels: map[string]string{internal.BpfProgramOwner: App.Name, internal.K8sHostLabel: fakeNode.Name}, + Finalizers: []string{internal.BpfNsApplicationControllerFinalizer}, + }, + Spec: bpfmaniov1alpha1.BpfProgramSpec{ + Type: "application", + }, + Status: bpfmaniov1alpha1.BpfProgramStatus{ + Conditions: []metav1.Condition{bpfmaniov1alpha1.BpfProgCondLoaded.Condition()}, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, App, expectedBpfProg} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, App) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(App).WithRuntimeObjects(objs...).Build() + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + } + + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ApplicationProgram object with the scheme and fake client. + r := &BpfNsApplicationReconciler{NamespaceProgramReconciler: npr} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should add the finalizer to the applicationProgram object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: App.Name, Namespace: App.Namespace}, App) + require.NoError(t, err) + + // Check the bpfman-operator finalizer was successfully added + require.Contains(t, App.GetFinalizers(), internal.BpfmanOperatorFinalizer) + + // NOTE: THIS IS A TEST FOR AN ERROR PATH. THERE SHOULD NEVER BE MORE THAN + // ONE CONDITION. + if multiCondition { + // Add some random conditions and verify that the condition still gets + // updated correctly. + meta.SetStatusCondition(&App.Status.Conditions, bpfmaniov1alpha1.ProgramDeleteError.Condition("bogus condition #1")) + if err := r.Status().Update(ctx, App); err != nil { + r.Logger.V(1).Info("failed to set App Ns Program object status") + } + meta.SetStatusCondition(&App.Status.Conditions, bpfmaniov1alpha1.ProgramReconcileError.Condition("bogus condition #2")) + if err := r.Status().Update(ctx, App); err != nil { + r.Logger.V(1).Info("failed to set App Ns Program object status") + } + // Make sure we have 2 conditions + require.Equal(t, 2, len(App.Status.Conditions)) + } + + // Second reconcile should check BpfNsProgram Status and write Success condition to TcNsProgram Status + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: App.Name, Namespace: App.Namespace}, App) + require.NoError(t, err) + + // Make sure we only have 1 condition now + require.Equal(t, 1, len(App.Status.Conditions)) + // Make sure it's the right one. + require.Equal(t, App.Status.Conditions[0].Type, string(bpfmaniov1alpha1.ProgramReconcileSuccess)) +} + +func TestAppNsProgramReconcile(t *testing.T) { + appNsProgramReconcile(t, false) +} + +func TestAppNsUpdateStatus(t *testing.T) { + appNsProgramReconcile(t, true) +} diff --git a/controllers/bpfman-operator/application-ns-programs.go b/controllers/bpfman-operator/application-ns-programs.go new file mode 100644 index 000000000..f56f669ce --- /dev/null +++ b/controllers/bpfman-operator/application-ns-programs.go @@ -0,0 +1,141 @@ +/* +Copyright 2024 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanoperator + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + internal "github.com/bpfman/bpfman-operator/internal" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfnsapplications,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfnsapplications/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfnsapplications/finalizers,verbs=update +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=bpfnsapplications,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=bpfnsapplications/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=bpfnsapplications/finalizers,verbs=update + +// BpfNsApplicationReconciler reconciles a BpfNsApplication object +type BpfNsApplicationReconciler struct { + NamespaceProgramReconciler +} + +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *BpfNsApplicationReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList] { + return &r.NamespaceProgramReconciler.ReconcilerCommon +} + +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *BpfNsApplicationReconciler) getFinalizer() string { + return internal.BpfNsApplicationControllerFinalizer +} + +// SetupWithManager sets up the controller with the Manager. +func (r *BpfNsApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.BpfNsApplication{}). + // Watch BpfNsPrograms which are owned by BpfNsApplications + Watches( + &bpfmaniov1alpha1.BpfNsProgram{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates( + predicate.And( + statusChangedPredicateNamespace(), + internal.BpfNsProgramTypePredicate(internal.ApplicationString), + ), + ), + ). + Complete(r) +} + +func (r *BpfNsApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Logger = ctrl.Log.WithName("application") + r.Logger.Info("bpfman-operator enter: application-ns", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + + appProgram := &bpfmaniov1alpha1.BpfNsApplication{} + if err := r.Get(ctx, req.NamespacedName, appProgram); err != nil { + // Reconcile was triggered by BpfNsProgram event, get parent appProgram Object. + if errors.IsNotFound(err) { + bpfProgram := &bpfmaniov1alpha1.BpfNsProgram{} + if err := r.Get(ctx, req.NamespacedName, bpfProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.V(1).Info("BpfNsProgram not found stale reconcile, exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting BpfNsProgram Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + // Get owning appProgram object from ownerRef + ownerRef := metav1.GetControllerOf(bpfProgram) + if ownerRef == nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting BpfNsProgram Object owner") + } + + if err := r.Get(ctx, types.NamespacedName{Namespace: req.NamespacedName.Namespace, Name: ownerRef.Name}, appProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.Info("Application Programs from ownerRef not found stale reconcile exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting Application Programs Object from ownerRef", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + } else { + r.Logger.Error(err, "failed getting Application Programs Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + return ctrl.Result{}, nil + } + } + + return reconcileBpfProgram(ctx, r, appProgram) +} + +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *BpfNsApplicationReconciler) updateStatus( + ctx context.Context, + namespace string, + name string, + cond bpfmaniov1alpha1.ProgramConditionType, + message string, +) (ctrl.Result, error) { + // Sometimes we end up with a stale FentryProgram due to races, do this + // get to ensure we're up to date before attempting a status update. + app := &bpfmaniov1alpha1.BpfNsApplication{} + if err := r.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, app); err != nil { + r.Logger.V(1).Info("failed to get fresh Application Programs object...requeuing") + return ctrl.Result{Requeue: true, RequeueAfter: retryDurationOperator}, nil + } + + return r.updateCondition(ctx, app, &app.Status.Conditions, cond, message) +} diff --git a/controllers/bpfman-operator/application-program_test.go b/controllers/bpfman-operator/application-program_test.go index 76fc0770f..a2f3d2a25 100644 --- a/controllers/bpfman-operator/application-program_test.go +++ b/controllers/bpfman-operator/application-program_test.go @@ -135,16 +135,20 @@ func appProgramReconcile(t *testing.T, multiCondition bool) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithStatusSubresource(App).WithRuntimeObjects(objs...).Build() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ApplicationProgram object with the scheme and fake client. - r := &BpfApplicationReconciler{ReconcilerCommon: rc} + r := &BpfApplicationReconciler{ClusterProgramReconciler: cpr} // Mock request to simulate Reconcile() being called on an event for a // watched resource . diff --git a/controllers/bpfman-operator/application-programs.go b/controllers/bpfman-operator/application-programs.go index 58fb7ecc7..2be810839 100644 --- a/controllers/bpfman-operator/application-programs.go +++ b/controllers/bpfman-operator/application-programs.go @@ -40,13 +40,15 @@ import ( // BpfApplicationReconciler reconciles a BpfApplication object type BpfApplicationReconciler struct { - ReconcilerCommon + ClusterProgramReconciler } -func (r *BpfApplicationReconciler) getRecCommon() *ReconcilerCommon { - return &r.ReconcilerCommon +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *BpfApplicationReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] { + return &r.ClusterProgramReconciler.ReconcilerCommon } +//lint:ignore U1000 Linter claims function unused, but generics confusing linter func (r *BpfApplicationReconciler) getFinalizer() string { return internal.BpfApplicationControllerFinalizer } @@ -59,7 +61,7 @@ func (r *BpfApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &bpfmaniov1alpha1.BpfProgram{}, &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.ApplicationString))), + builder.WithPredicates(predicate.And(statusChangedPredicateCluster(), internal.BpfProgramTypePredicate(internal.ApplicationString))), ). Complete(r) } @@ -106,7 +108,8 @@ func (r *BpfApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Reque return reconcileBpfProgram(ctx, r, appProgram) } -func (r *BpfApplicationReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *BpfApplicationReconciler) updateStatus(ctx context.Context, _namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { // Sometimes we end up with a stale FentryProgram due to races, do this // get to ensure we're up to date before attempting a status update. app := &bpfmaniov1alpha1.BpfApplication{} diff --git a/controllers/bpfman-operator/common.go b/controllers/bpfman-operator/common.go index 3c7d9ec1c..38061e02d 100644 --- a/controllers/bpfman-operator/common.go +++ b/controllers/bpfman-operator/common.go @@ -19,7 +19,6 @@ package bpfmanoperator import ( "context" "fmt" - "reflect" "time" corev1 "k8s.io/api/core/v1" @@ -29,8 +28,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/predicate" bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" internal "github.com/bpfman/bpfman-operator/internal" @@ -39,47 +36,73 @@ import ( ) //+kubebuilder:rbac:groups=bpfman.io,resources=bpfprograms,verbs=get;list;watch -//+kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch +//+kubebuilder:rbac:groups=bpfman.io,resources=bpfnsprograms,verbs=get;list;watch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=bpfnsprograms,verbs=get;list;watch +// +kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch const ( retryDurationOperator = 5 * time.Second ) +type BpfProgOper interface { + GetName() string + + GetLabels() map[string]string + GetStatus() *bpfmaniov1alpha1.BpfProgramStatus +} + +type BpfProgListOper[T any] interface { + // bpfmniov1alpha1.BpfProgramList | bpfmaniov1alpha1.BpfNsProgramList + + GetItems() []T +} + // ReconcilerCommon reconciles a BpfProgram object -type ReconcilerCommon struct { +type ReconcilerCommon[T BpfProgOper, TL BpfProgListOper[T]] struct { client.Client Scheme *runtime.Scheme Logger logr.Logger } // bpfmanReconciler defines a k8s reconciler which can program bpfman. -type ProgramReconciler interface { - getRecCommon() *ReconcilerCommon +type ProgramReconciler[T BpfProgOper, TL BpfProgListOper[T]] interface { + // BPF Cluster of Namespaced Reconciler + getBpfList(ctx context.Context, + progName string, + progNamespace string, + ) (*TL, error) + containsFinalizer(bpfProgram *T, finalizer string) bool + + // *Program Reconciler + getRecCommon() *ReconcilerCommon[T, TL] updateStatus(ctx context.Context, + namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) getFinalizer() string } -func reconcileBpfProgram(ctx context.Context, rec ProgramReconciler, prog client.Object) (ctrl.Result, error) { +func reconcileBpfProgram[T BpfProgOper, TL BpfProgListOper[T]]( + ctx context.Context, + rec ProgramReconciler[T, TL], + prog client.Object, +) (ctrl.Result, error) { r := rec.getRecCommon() progName := prog.GetName() + progNamespace := prog.GetNamespace() - r.Logger.V(1).Info("Reconciling Program", "Name", progName) + r.Logger.V(1).Info("Reconciling Program", "Namespace", progNamespace, "Name", progName) if !controllerutil.ContainsFinalizer(prog, internal.BpfmanOperatorFinalizer) { + r.Logger.V(1).Info("Add Finalizer", "Namespace", progNamespace, "ProgramName", progName) return r.addFinalizer(ctx, prog, internal.BpfmanOperatorFinalizer) } // reconcile Program Object on all other events // list all existing bpfProgram state for the given Program - bpfPrograms := &bpfmaniov1alpha1.BpfProgramList{} - - // Only list bpfPrograms for this Program - opts := []client.ListOption{client.MatchingLabels{internal.BpfProgramOwner: progName}} - - if err := r.List(ctx, bpfPrograms, opts...); err != nil { + bpfPrograms, err := rec.getBpfList(ctx, progName, progNamespace) + if err != nil { r.Logger.Error(err, "failed to get freshPrograms for full reconcile") return ctrl.Result{}, nil } @@ -96,7 +119,7 @@ func reconcileBpfProgram(ctx context.Context, rec ProgramReconciler, prog client if prog.GetDeletionTimestamp().IsZero() { for _, node := range nodes.Items { nodeFound := false - for _, program := range bpfPrograms.Items { + for _, program := range (*bpfPrograms).GetItems() { bpfProgramNode := program.GetLabels()[internal.K8sHostLabel] if node.Name == bpfProgramNode { nodeFound = true @@ -104,7 +127,7 @@ func reconcileBpfProgram(ctx context.Context, rec ProgramReconciler, prog client } } if !nodeFound { - return rec.updateStatus(ctx, progName, bpfmaniov1alpha1.ProgramNotYetLoaded, "") + return rec.updateStatus(ctx, progNamespace, progName, bpfmaniov1alpha1.ProgramNotYetLoaded, "") } } } @@ -112,14 +135,15 @@ func reconcileBpfProgram(ctx context.Context, rec ProgramReconciler, prog client failedBpfPrograms := []string{} finalApplied := []string{} // Make sure no bpfPrograms had any issues in the loading or unloading process - for _, bpfProgram := range bpfPrograms.Items { + for _, bpfProgram := range (*bpfPrograms).GetItems() { - if controllerutil.ContainsFinalizer(&bpfProgram, rec.getFinalizer()) { - finalApplied = append(finalApplied, bpfProgram.Name) + if rec.containsFinalizer(&bpfProgram, rec.getFinalizer()) { + finalApplied = append(finalApplied, bpfProgram.GetName()) } - if bpfmanHelpers.IsBpfProgramConditionFailure(&bpfProgram.Status.Conditions) { - failedBpfPrograms = append(failedBpfPrograms, bpfProgram.Name) + status := bpfProgram.GetStatus() + if bpfmanHelpers.IsBpfProgramConditionFailure(&status.Conditions) { + failedBpfPrograms = append(failedBpfPrograms, bpfProgram.GetName()) } } @@ -132,21 +156,21 @@ func reconcileBpfProgram(ctx context.Context, rec ProgramReconciler, prog client } // Causes Requeue - return rec.updateStatus(ctx, progName, bpfmaniov1alpha1.ProgramDeleteError, fmt.Sprintf("Program Deletion failed on the following bpfProgram Objects: %v", - finalApplied)) + return rec.updateStatus(ctx, progNamespace, progName, bpfmaniov1alpha1.ProgramDeleteError, + fmt.Sprintf("Program Deletion failed on the following bpfProgram Objects: %v", finalApplied)) } if len(failedBpfPrograms) != 0 { // Causes Requeue - return rec.updateStatus(ctx, progName, bpfmaniov1alpha1.ProgramReconcileError, + return rec.updateStatus(ctx, progNamespace, progName, bpfmaniov1alpha1.ProgramReconcileError, fmt.Sprintf("bpfProgramReconciliation failed on the following bpfProgram Objects: %v", failedBpfPrograms)) } // Causes Requeue - return rec.updateStatus(ctx, progName, bpfmaniov1alpha1.ProgramReconcileSuccess, "") + return rec.updateStatus(ctx, progNamespace, progName, bpfmaniov1alpha1.ProgramReconcileSuccess, "") } -func (r *ReconcilerCommon) removeFinalizer(ctx context.Context, prog client.Object, finalizer string) (ctrl.Result, error) { +func (r *ReconcilerCommon[T, TL]) removeFinalizer(ctx context.Context, prog client.Object, finalizer string) (ctrl.Result, error) { r.Logger.Info("Calling KubeAPI to delete Program Finalizer", "Type", prog.GetObjectKind().GroupVersionKind().Kind, "Name", prog.GetName()) if changed := controllerutil.RemoveFinalizer(prog, finalizer); changed { @@ -160,7 +184,7 @@ func (r *ReconcilerCommon) removeFinalizer(ctx context.Context, prog client.Obje return ctrl.Result{}, nil } -func (r *ReconcilerCommon) addFinalizer(ctx context.Context, prog client.Object, finalizer string) (ctrl.Result, error) { +func (r *ReconcilerCommon[T, TL]) addFinalizer(ctx context.Context, prog client.Object, finalizer string) (ctrl.Result, error) { controllerutil.AddFinalizer(prog, finalizer) r.Logger.Info("Calling KubeAPI to add Program Finalizer", "Type", prog.GetObjectKind().GroupVersionKind().Kind, "Name", prog.GetName()) @@ -173,27 +197,13 @@ func (r *ReconcilerCommon) addFinalizer(ctx context.Context, prog client.Object, return ctrl.Result{}, nil } -// Only reconcile if a bpfprogram object's status has been updated. -func statusChangedPredicate() predicate.Funcs { - return predicate.Funcs{ - GenericFunc: func(e event.GenericEvent) bool { - return false - }, - CreateFunc: func(e event.CreateEvent) bool { - return false - }, - UpdateFunc: func(e event.UpdateEvent) bool { - oldObject := e.ObjectOld.(*bpfmaniov1alpha1.BpfProgram) - newObject := e.ObjectNew.(*bpfmaniov1alpha1.BpfProgram) - return !reflect.DeepEqual(oldObject.Status, newObject.Status) - }, - DeleteFunc: func(e event.DeleteEvent) bool { - return false - }, - } -} - -func (r *ReconcilerCommon) updateCondition(ctx context.Context, obj client.Object, conditions *[]metav1.Condition, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +func (r *ReconcilerCommon[T, TL]) updateCondition( + ctx context.Context, + obj client.Object, + conditions *[]metav1.Condition, + cond bpfmaniov1alpha1.ProgramConditionType, + message string, +) (ctrl.Result, error) { r.Logger.V(1).Info("updateCondition()", "existing conds", conditions, "new cond", cond) diff --git a/controllers/bpfman-operator/common_cluster.go b/controllers/bpfman-operator/common_cluster.go new file mode 100644 index 000000000..1e1a6cb06 --- /dev/null +++ b/controllers/bpfman-operator/common_cluster.go @@ -0,0 +1,83 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanoperator + +import ( + "context" + "reflect" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + internal "github.com/bpfman/bpfman-operator/internal" +) + +type ClusterProgramReconciler struct { + ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] +} + +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *ClusterProgramReconciler) getBpfList( + ctx context.Context, + progName string, + _progNamespace string, +) (*bpfmaniov1alpha1.BpfProgramList, error) { + + bpfProgramList := &bpfmaniov1alpha1.BpfProgramList{} + + // Only list bpfPrograms for this Program + opts := []client.ListOption{ + client.MatchingLabels{internal.BpfProgramOwner: progName}, + } + + err := r.List(ctx, bpfProgramList, opts...) + if err != nil { + return nil, err + } + + return bpfProgramList, nil +} + +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *ClusterProgramReconciler) containsFinalizer( + bpfProgram *bpfmaniov1alpha1.BpfProgram, + finalizer string, +) bool { + return controllerutil.ContainsFinalizer(bpfProgram, finalizer) +} + +func statusChangedPredicateCluster() predicate.Funcs { + return predicate.Funcs{ + GenericFunc: func(e event.GenericEvent) bool { + return false + }, + CreateFunc: func(e event.CreateEvent) bool { + return false + }, + UpdateFunc: func(e event.UpdateEvent) bool { + oldObject := e.ObjectOld.(*bpfmaniov1alpha1.BpfProgram) + newObject := e.ObjectNew.(*bpfmaniov1alpha1.BpfProgram) + return !reflect.DeepEqual(oldObject.GetStatus(), newObject.Status) + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return false + }, + } +} diff --git a/controllers/bpfman-operator/common_namespace.go b/controllers/bpfman-operator/common_namespace.go new file mode 100644 index 000000000..a3dc65677 --- /dev/null +++ b/controllers/bpfman-operator/common_namespace.go @@ -0,0 +1,84 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanoperator + +import ( + "context" + "reflect" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + internal "github.com/bpfman/bpfman-operator/internal" +) + +type NamespaceProgramReconciler struct { + ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList] +} + +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *NamespaceProgramReconciler) getBpfList( + ctx context.Context, + progName string, + progNamespace string, +) (*bpfmaniov1alpha1.BpfNsProgramList, error) { + + bpfProgramList := &bpfmaniov1alpha1.BpfNsProgramList{} + + // Only list bpfPrograms for this Program + opts := []client.ListOption{ + client.MatchingLabels{internal.BpfProgramOwner: progName}, + client.InNamespace(progNamespace), + } + + err := r.List(ctx, bpfProgramList, opts...) + if err != nil { + return nil, err + } + + return bpfProgramList, nil +} + +//lint:ignore U1000 Linter claims function unused, but generics confusing linter +func (r *NamespaceProgramReconciler) containsFinalizer( + bpfProgram *bpfmaniov1alpha1.BpfNsProgram, + finalizer string, +) bool { + return controllerutil.ContainsFinalizer(bpfProgram, finalizer) +} + +func statusChangedPredicateNamespace() predicate.Funcs { + return predicate.Funcs{ + GenericFunc: func(e event.GenericEvent) bool { + return false + }, + CreateFunc: func(e event.CreateEvent) bool { + return false + }, + UpdateFunc: func(e event.UpdateEvent) bool { + oldObject := e.ObjectOld.(*bpfmaniov1alpha1.BpfNsProgram) + newObject := e.ObjectNew.(*bpfmaniov1alpha1.BpfNsProgram) + return !reflect.DeepEqual(oldObject.GetStatus(), newObject.Status) + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return false + }, + } +} diff --git a/controllers/bpfman-operator/configmap.go b/controllers/bpfman-operator/configmap.go index a351eca7c..fb254a559 100644 --- a/controllers/bpfman-operator/configmap.go +++ b/controllers/bpfman-operator/configmap.go @@ -48,7 +48,7 @@ import ( // +kubebuilder:rbac:groups=bpfman.io,resources=configmaps/finalizers,verbs=update type BpfmanConfigReconciler struct { - ReconcilerCommon + ClusterProgramReconciler BpfmanStandardDeployment string CsiDriverDeployment string RestrictedSCC string diff --git a/controllers/bpfman-operator/configmap_test.go b/controllers/bpfman-operator/configmap_test.go index 8bb153eae..9b1bbb7c0 100644 --- a/controllers/bpfman-operator/configmap_test.go +++ b/controllers/bpfman-operator/configmap_test.go @@ -40,6 +40,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/reconcile" + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" "github.com/bpfman/bpfman-operator/internal" ) @@ -92,10 +93,19 @@ func setupTestEnvironment(isOpenShift bool) (*BpfmanConfigReconciler, *corev1.Co // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ + Client: cl, + Scheme: s, + } + + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Create a BpfmanConfigReconciler object with the scheme and // fake client. r := &BpfmanConfigReconciler{ - ReconcilerCommon: ReconcilerCommon{Client: cl, Scheme: s}, + ClusterProgramReconciler: cpr, BpfmanStandardDeployment: resolveConfigPath(internal.BpfmanDaemonManifestPath), CsiDriverDeployment: resolveConfigPath(internal.BpfmanCsiDriverPath), IsOpenshift: isOpenShift, diff --git a/controllers/bpfman-operator/fentry-program.go b/controllers/bpfman-operator/fentry-program.go index f7c5c7ab0..feec90b34 100644 --- a/controllers/bpfman-operator/fentry-program.go +++ b/controllers/bpfman-operator/fentry-program.go @@ -11,6 +11,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanoperator import ( @@ -35,11 +37,11 @@ import ( //+kubebuilder:rbac:groups=bpfman.io,resources=fentryprograms/finalizers,verbs=update type FentryProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler } -func (r *FentryProgramReconciler) getRecCommon() *ReconcilerCommon { - return &r.ReconcilerCommon +func (r *FentryProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] { + return &r.ClusterProgramReconciler.ReconcilerCommon } func (r *FentryProgramReconciler) getFinalizer() string { @@ -54,7 +56,7 @@ func (r *FentryProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &bpfmaniov1alpha1.BpfProgram{}, &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.FentryString))), + builder.WithPredicates(predicate.And(statusChangedPredicateCluster(), internal.BpfProgramTypePredicate(internal.FentryString))), ). Complete(r) } @@ -101,7 +103,7 @@ func (r *FentryProgramReconciler) Reconcile(ctx context.Context, req ctrl.Reques return reconcileBpfProgram(ctx, r, fentryProgram) } -func (r *FentryProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +func (r *FentryProgramReconciler) updateStatus(ctx context.Context, _namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { // Sometimes we end up with a stale FentryProgram due to races, do this // get to ensure we're up to date before attempting a status update. prog := &bpfmaniov1alpha1.FentryProgram{} diff --git a/controllers/bpfman-operator/fentry-program_test.go b/controllers/bpfman-operator/fentry-program_test.go index 1ffd18af7..335894de0 100644 --- a/controllers/bpfman-operator/fentry-program_test.go +++ b/controllers/bpfman-operator/fentry-program_test.go @@ -105,16 +105,19 @@ func fentryProgramReconcile(t *testing.T, multiCondition bool) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithStatusSubresource(Fentry).WithRuntimeObjects(objs...).Build() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a FentryProgram object with the scheme and fake client. - r := &FentryProgramReconciler{ReconcilerCommon: rc} + r := &FentryProgramReconciler{ClusterProgramReconciler: cpr} // Mock request to simulate Reconcile() being called on an event for a // watched resource . diff --git a/controllers/bpfman-operator/fexit-program.go b/controllers/bpfman-operator/fexit-program.go index 7a5ec42c9..19eafd8a3 100644 --- a/controllers/bpfman-operator/fexit-program.go +++ b/controllers/bpfman-operator/fexit-program.go @@ -11,6 +11,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanoperator import ( @@ -35,11 +37,11 @@ import ( //+kubebuilder:rbac:groups=bpfman.io,resources=fexitprograms/finalizers,verbs=update type FexitProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler } -func (r *FexitProgramReconciler) getRecCommon() *ReconcilerCommon { - return &r.ReconcilerCommon +func (r *FexitProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] { + return &r.ClusterProgramReconciler.ReconcilerCommon } func (r *FexitProgramReconciler) getFinalizer() string { @@ -54,7 +56,7 @@ func (r *FexitProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &bpfmaniov1alpha1.BpfProgram{}, &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.FexitString))), + builder.WithPredicates(predicate.And(statusChangedPredicateCluster(), internal.BpfProgramTypePredicate(internal.FexitString))), ). Complete(r) } @@ -101,7 +103,7 @@ func (r *FexitProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request return reconcileBpfProgram(ctx, r, fexitProgram) } -func (r *FexitProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +func (r *FexitProgramReconciler) updateStatus(ctx context.Context, _namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { // Sometimes we end up with a stale FexitProgram due to races, do this // get to ensure we're up to date before attempting a status update. prog := &bpfmaniov1alpha1.FexitProgram{} diff --git a/controllers/bpfman-operator/fexit-program_test.go b/controllers/bpfman-operator/fexit-program_test.go index 086e30d9d..d2077ece4 100644 --- a/controllers/bpfman-operator/fexit-program_test.go +++ b/controllers/bpfman-operator/fexit-program_test.go @@ -105,16 +105,20 @@ func fexitProgramReconcile(t *testing.T, multiCondition bool) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithStatusSubresource(Fexit).WithRuntimeObjects(objs...).Build() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a FexitProgram object with the scheme and fake client. - r := &FexitProgramReconciler{ReconcilerCommon: rc} + r := &FexitProgramReconciler{ClusterProgramReconciler: cpr} // Mock request to simulate Reconcile() being called on an event for a // watched resource . diff --git a/controllers/bpfman-operator/kprobe-program.go b/controllers/bpfman-operator/kprobe-program.go index 71623e6e2..2cfa6b6e0 100644 --- a/controllers/bpfman-operator/kprobe-program.go +++ b/controllers/bpfman-operator/kprobe-program.go @@ -11,6 +11,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanoperator import ( @@ -35,11 +37,11 @@ import ( //+kubebuilder:rbac:groups=bpfman.io,resources=kprobeprograms/finalizers,verbs=update type KprobeProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler } -func (r *KprobeProgramReconciler) getRecCommon() *ReconcilerCommon { - return &r.ReconcilerCommon +func (r *KprobeProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] { + return &r.ClusterProgramReconciler.ReconcilerCommon } func (r *KprobeProgramReconciler) getFinalizer() string { @@ -54,7 +56,7 @@ func (r *KprobeProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &bpfmaniov1alpha1.BpfProgram{}, &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.Kprobe.String()))), + builder.WithPredicates(predicate.And(statusChangedPredicateCluster(), internal.BpfProgramTypePredicate(internal.Kprobe.String()))), ). Complete(r) } @@ -101,7 +103,7 @@ func (r *KprobeProgramReconciler) Reconcile(ctx context.Context, req ctrl.Reques return reconcileBpfProgram(ctx, r, kprobeProgram) } -func (r *KprobeProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +func (r *KprobeProgramReconciler) updateStatus(ctx context.Context, _namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { // Sometimes we end up with a stale KprobeProgram due to races, do this // get to ensure we're up to date before attempting a status update. prog := &bpfmaniov1alpha1.KprobeProgram{} diff --git a/controllers/bpfman-operator/kprobe-program_test.go b/controllers/bpfman-operator/kprobe-program_test.go index 5216fbd77..c0864dc58 100644 --- a/controllers/bpfman-operator/kprobe-program_test.go +++ b/controllers/bpfman-operator/kprobe-program_test.go @@ -109,16 +109,20 @@ func kprobeProgramReconcile(t *testing.T, multiCondition bool) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithStatusSubresource(Kprobe).WithRuntimeObjects(objs...).Build() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a KprobeProgram object with the scheme and fake client. - r := &KprobeProgramReconciler{ReconcilerCommon: rc} + r := &KprobeProgramReconciler{ClusterProgramReconciler: cpr} // Mock request to simulate Reconcile() being called on an event for a // watched resource . diff --git a/controllers/bpfman-operator/tc-ns-program.go b/controllers/bpfman-operator/tc-ns-program.go new file mode 100644 index 000000000..69961786b --- /dev/null +++ b/controllers/bpfman-operator/tc-ns-program.go @@ -0,0 +1,134 @@ +/* +Copyright 2024. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanoperator + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman-operator/internal" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +type TcNsProgramReconciler struct { + NamespaceProgramReconciler +} + +func (r *TcNsProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList] { + return &r.NamespaceProgramReconciler.ReconcilerCommon +} + +func (r *TcNsProgramReconciler) getFinalizer() string { + return internal.TcNsProgramControllerFinalizer +} + +// SetupWithManager sets up the controller with the Manager. +func (r *TcNsProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.TcNsProgram{}). + // Watch BpfNsPrograms which are owned by TcNsPrograms + Watches( + &bpfmaniov1alpha1.BpfNsProgram{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates( + predicate.And( + statusChangedPredicateNamespace(), + internal.BpfNsProgramTypePredicate(internal.Tc.String()), + ), + ), + ). + Complete(r) +} + +//+kubebuilder:rbac:groups=bpfman.io,resources=tcnsprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,resources=tcnsprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,resources=tcnsprograms/finalizers,verbs=update +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=tcnsprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=tcnsprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=tcnsprograms/finalizers,verbs=update + +func (r *TcNsProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Logger = ctrl.Log.WithName("tc-ns") + r.Logger.Info("bpfman-operator enter: tc-ns", "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + + tcProgram := &bpfmaniov1alpha1.TcNsProgram{} + if err := r.Get(ctx, req.NamespacedName, tcProgram); err != nil { + // list all TcNsProgram objects with + if errors.IsNotFound(err) { + bpfProgram := &bpfmaniov1alpha1.BpfNsProgram{} + if err := r.Get(ctx, req.NamespacedName, bpfProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.V(1).Info("BpfNsProgram not found stale reconcile, exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting BpfNsProgram Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + // Get owning TcNsProgram object from ownerRef + ownerRef := metav1.GetControllerOf(bpfProgram) + if ownerRef == nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting BpfNsProgram Object owner") + } + + if err := r.Get(ctx, types.NamespacedName{Namespace: req.NamespacedName.Namespace, Name: ownerRef.Name}, tcProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.Info("TcNsProgram from ownerRef not found stale reconcile exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting TcNsProgram Object from ownerRef", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + } else { + r.Logger.Error(err, "failed getting TcNsProgram Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + return ctrl.Result{}, nil + } + } + + return reconcileBpfProgram(ctx, r, tcProgram) +} + +func (r *TcNsProgramReconciler) updateStatus( + ctx context.Context, + namespace string, + name string, + cond bpfmaniov1alpha1.ProgramConditionType, + message string, +) (ctrl.Result, error) { + // Sometimes we end up with a stale TcNsProgram due to races, do this + // get to ensure we're up to date before attempting a finalizer removal. + prog := &bpfmaniov1alpha1.TcNsProgram{} + if err := r.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, prog); err != nil { + r.Logger.V(1).Info("failed to get fresh TcNsProgram object...requeuing") + return ctrl.Result{Requeue: true, RequeueAfter: retryDurationOperator}, nil + } + + return r.updateCondition(ctx, prog, &prog.Status.Conditions, cond, message) +} diff --git a/controllers/bpfman-operator/tc-ns-program_test.go b/controllers/bpfman-operator/tc-ns-program_test.go new file mode 100644 index 000000000..125a3e7c5 --- /dev/null +++ b/controllers/bpfman-operator/tc-ns-program_test.go @@ -0,0 +1,176 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanoperator + +import ( + "context" + "fmt" + "testing" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestTcNsProgramReconcile(t *testing.T) { + var ( + name = "fakeTcNsProgram" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + direction = "ingress" + fakeNode = testutils.NewNode("fake-control-plane") + fakeInt = "eth0" + ctx = context.TODO() + bpfProgName = fmt.Sprintf("%s-%s", name, fakeNode.Name) + ) + + // A TcNsProgram object with metadata and spec. + tc := &bpfmaniov1alpha1.TcNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: bpfmaniov1alpha1.TcNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + TcNsProgramInfo: bpfmaniov1alpha1.TcNsProgramInfo{ + + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &[]string{fakeInt}, + }, + Priority: 0, + Direction: direction, + ProceedOn: []bpfmaniov1alpha1.TcProceedOnValue{ + bpfmaniov1alpha1.TcProceedOnValue("pipe"), + bpfmaniov1alpha1.TcProceedOnValue("dispatcher_return"), + }, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + }, + }, + } + + // The expected accompanying BpfNsProgram object + expectedBpfProg := &bpfmaniov1alpha1.BpfNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: bpfProgName, + OwnerReferences: []metav1.OwnerReference{ + { + Name: tc.Name, + Controller: &[]bool{true}[0], + }, + }, + Labels: map[string]string{internal.BpfProgramOwner: tc.Name, internal.K8sHostLabel: fakeNode.Name}, + Finalizers: []string{internal.TcNsProgramControllerFinalizer}, + }, + Spec: bpfmaniov1alpha1.BpfProgramSpec{ + Type: "tc", + }, + Status: bpfmaniov1alpha1.BpfProgramStatus{ + Conditions: []metav1.Condition{bpfmaniov1alpha1.BpfProgCondLoaded.Condition()}, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, tc, expectedBpfProg} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, tc) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(tc).WithRuntimeObjects(objs...).Build() + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + } + + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &TcNsProgramReconciler{NamespaceProgramReconciler: npr} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + }, + } + + // First reconcile should add the finalzier to the TcNsProgram object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: tc.Name, Namespace: tc.Namespace}, tc) + require.NoError(t, err) + + // Check the bpfman-operator finalizer was successfully added + require.Contains(t, tc.GetFinalizers(), internal.BpfmanOperatorFinalizer) + + // Second reconcile should check BpfNsProgram Status and write Success condition to TcNsProgram Status + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: tc.Name, Namespace: tc.Namespace}, tc) + require.NoError(t, err) + + require.Equal(t, tc.Status.Conditions[0].Type, string(bpfmaniov1alpha1.ProgramReconcileSuccess)) + +} diff --git a/controllers/bpfman-operator/tc-program.go b/controllers/bpfman-operator/tc-program.go index f8c5db136..5719762f1 100644 --- a/controllers/bpfman-operator/tc-program.go +++ b/controllers/bpfman-operator/tc-program.go @@ -11,6 +11,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanoperator import ( @@ -31,11 +33,11 @@ import ( ) type TcProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler } -func (r *TcProgramReconciler) getRecCommon() *ReconcilerCommon { - return &r.ReconcilerCommon +func (r *TcProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] { + return &r.ClusterProgramReconciler.ReconcilerCommon } func (r *TcProgramReconciler) getFinalizer() string { @@ -50,7 +52,7 @@ func (r *TcProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &bpfmaniov1alpha1.BpfProgram{}, &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.Tc.String()))), + builder.WithPredicates(predicate.And(statusChangedPredicateCluster(), internal.BpfProgramTypePredicate(internal.Tc.String()))), ). Complete(r) } @@ -101,7 +103,7 @@ func (r *TcProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return reconcileBpfProgram(ctx, r, tcProgram) } -func (r *TcProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +func (r *TcProgramReconciler) updateStatus(ctx context.Context, _namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { // Sometimes we end up with a stale TcProgram due to races, do this // get to ensure we're up to date before attempting a finalizer removal. prog := &bpfmaniov1alpha1.TcProgram{} diff --git a/controllers/bpfman-operator/tc-program_test.go b/controllers/bpfman-operator/tc-program_test.go index 032a4d7f8..8226e95a5 100644 --- a/controllers/bpfman-operator/tc-program_test.go +++ b/controllers/bpfman-operator/tc-program_test.go @@ -112,16 +112,20 @@ func TestTcProgramReconcile(t *testing.T) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithStatusSubresource(tc).WithRuntimeObjects(objs...).Build() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &TcProgramReconciler{ReconcilerCommon: rc} + r := &TcProgramReconciler{ClusterProgramReconciler: cpr} // Mock request to simulate Reconcile() being called on an event for a // watched resource . diff --git a/controllers/bpfman-operator/tcx-ns-program.go b/controllers/bpfman-operator/tcx-ns-program.go new file mode 100644 index 000000000..afd465af3 --- /dev/null +++ b/controllers/bpfman-operator/tcx-ns-program.go @@ -0,0 +1,133 @@ +/* +Copyright 2024. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanoperator + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman-operator/internal" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +type TcxNsProgramReconciler struct { + NamespaceProgramReconciler +} + +func (r *TcxNsProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList] { + return &r.NamespaceProgramReconciler.ReconcilerCommon +} + +func (r *TcxNsProgramReconciler) getFinalizer() string { + return internal.TcxNsProgramControllerFinalizer +} + +// SetupWithManager sets up the controller with the Manager. +func (r *TcxNsProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.TcxNsProgram{}). + // Watch BpfNsPrograms which are owned by TcxNsPrograms + Watches( + &bpfmaniov1alpha1.BpfNsProgram{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And( + statusChangedPredicateNamespace(), + internal.BpfNsProgramTypePredicate(internal.TcxString), + ), + ), + ). + Complete(r) +} + +//+kubebuilder:rbac:groups=bpfman.io,resources=tcxnsprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,resources=tcxnsprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,resources=tcxnsprograms/finalizers,verbs=update +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=tcxnsprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=tcxnsprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=tcxnsprograms/finalizers,verbs=update + +func (r *TcxNsProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Logger = ctrl.Log.WithName("tcx-ns") + r.Logger.Info("bpfman-operator enter: tcx-ns", "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + + tcxProgram := &bpfmaniov1alpha1.TcxNsProgram{} + if err := r.Get(ctx, req.NamespacedName, tcxProgram); err != nil { + // list all TcxNsProgram objects with + if errors.IsNotFound(err) { + bpfProgram := &bpfmaniov1alpha1.BpfNsProgram{} + if err := r.Get(ctx, req.NamespacedName, bpfProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.V(1).Info("BpfNsProgram not found stale reconcile, exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting BpfNsProgram Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + // Get owning TcxNsProgram object from ownerRef + ownerRef := metav1.GetControllerOf(bpfProgram) + if ownerRef == nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting BpfNsProgram Object owner") + } + + if err := r.Get(ctx, types.NamespacedName{Namespace: req.NamespacedName.Namespace, Name: ownerRef.Name}, tcxProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.Info("TcxNsProgram from ownerRef not found stale reconcile exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting TcxNsProgram Object from ownerRef", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + } else { + r.Logger.Error(err, "failed getting TcxNsProgram Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + return ctrl.Result{}, nil + } + } + + return reconcileBpfProgram(ctx, r, tcxProgram) +} + +func (r *TcxNsProgramReconciler) updateStatus( + ctx context.Context, + namespace string, + name string, + cond bpfmaniov1alpha1.ProgramConditionType, + message string, +) (ctrl.Result, error) { + // Sometimes we end up with a stale TcxNsProgram due to races, do this + // get to ensure we're up to date before attempting a finalizer removal. + prog := &bpfmaniov1alpha1.TcxNsProgram{} + if err := r.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, prog); err != nil { + r.Logger.V(1).Info("failed to get fresh TcxNsProgram object...requeuing") + return ctrl.Result{Requeue: true, RequeueAfter: retryDurationOperator}, nil + } + + return r.updateCondition(ctx, prog, &prog.Status.Conditions, cond, message) +} diff --git a/controllers/bpfman-operator/tcx-ns-program_test.go b/controllers/bpfman-operator/tcx-ns-program_test.go new file mode 100644 index 000000000..440e3c439 --- /dev/null +++ b/controllers/bpfman-operator/tcx-ns-program_test.go @@ -0,0 +1,172 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanoperator + +import ( + "context" + "fmt" + "testing" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestTcxNsProgramReconcile(t *testing.T) { + var ( + name = "fakeTcProgram" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + direction = "ingress" + fakeNode = testutils.NewNode("fake-control-plane") + fakeInt = "eth0" + ctx = context.TODO() + bpfProgName = fmt.Sprintf("%s-%s", name, fakeNode.Name) + ) + + // A TcxNsProgram object with metadata and spec. + tcx := &bpfmaniov1alpha1.TcxNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: bpfmaniov1alpha1.TcxNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + TcxNsProgramInfo: bpfmaniov1alpha1.TcxNsProgramInfo{ + + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &[]string{fakeInt}, + }, + Priority: 0, + Direction: direction, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + }, + }, + } + + // The expected accompanying BpfNsProgram object + expectedBpfProg := &bpfmaniov1alpha1.BpfNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: bpfProgName, + OwnerReferences: []metav1.OwnerReference{ + { + Name: tcx.Name, + Controller: &[]bool{true}[0], + }, + }, + Labels: map[string]string{internal.BpfProgramOwner: tcx.Name, internal.K8sHostLabel: fakeNode.Name}, + Finalizers: []string{internal.TcxNsProgramControllerFinalizer}, + }, + Spec: bpfmaniov1alpha1.BpfProgramSpec{ + Type: "tcx", + }, + Status: bpfmaniov1alpha1.BpfProgramStatus{ + Conditions: []metav1.Condition{bpfmaniov1alpha1.BpfProgCondLoaded.Condition()}, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, tcx, expectedBpfProg} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, tcx) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(tcx).WithRuntimeObjects(objs...).Build() + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + } + + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &TcxNsProgramReconciler{NamespaceProgramReconciler: npr} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + }, + } + + // First reconcile should add the finalzier to the tcProgram object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: tcx.Name, Namespace: tcx.Namespace}, tcx) + require.NoError(t, err) + + // Check the bpfman-operator finalizer was successfully added + require.Contains(t, tcx.GetFinalizers(), internal.BpfmanOperatorFinalizer) + + // Second reconcile should check BpfNsProgram Status and write Success condition to tcProgram Status + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: tcx.Name, Namespace: tcx.Namespace}, tcx) + require.NoError(t, err) + + require.Equal(t, tcx.Status.Conditions[0].Type, string(bpfmaniov1alpha1.ProgramReconcileSuccess)) + +} diff --git a/controllers/bpfman-operator/tcx-program.go b/controllers/bpfman-operator/tcx-program.go index a13dc4f9d..57303f8d1 100644 --- a/controllers/bpfman-operator/tcx-program.go +++ b/controllers/bpfman-operator/tcx-program.go @@ -11,6 +11,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanoperator import ( @@ -31,11 +33,11 @@ import ( ) type TcxProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler } -func (r *TcxProgramReconciler) getRecCommon() *ReconcilerCommon { - return &r.ReconcilerCommon +func (r *TcxProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] { + return &r.ClusterProgramReconciler.ReconcilerCommon } func (r *TcxProgramReconciler) getFinalizer() string { @@ -50,7 +52,7 @@ func (r *TcxProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &bpfmaniov1alpha1.BpfProgram{}, &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.TcxString))), + builder.WithPredicates(predicate.And(statusChangedPredicateCluster(), internal.BpfProgramTypePredicate(internal.TcxString))), ). Complete(r) } @@ -101,7 +103,7 @@ func (r *TcxProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) return reconcileBpfProgram(ctx, r, tcxProgram) } -func (r *TcxProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +func (r *TcxProgramReconciler) updateStatus(ctx context.Context, _namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { // Sometimes we end up with a stale TcxProgram due to races, do this // get to ensure we're up to date before attempting a finalizer removal. prog := &bpfmaniov1alpha1.TcxProgram{} diff --git a/controllers/bpfman-operator/tcx-program_test.go b/controllers/bpfman-operator/tcx-program_test.go index 5623e995f..1a042b320 100644 --- a/controllers/bpfman-operator/tcx-program_test.go +++ b/controllers/bpfman-operator/tcx-program_test.go @@ -108,16 +108,20 @@ func TestTcxProgramReconcile(t *testing.T) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithStatusSubresource(tcx).WithRuntimeObjects(objs...).Build() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &TcxProgramReconciler{ReconcilerCommon: rc} + r := &TcxProgramReconciler{ClusterProgramReconciler: cpr} // Mock request to simulate Reconcile() being called on an event for a // watched resource . diff --git a/controllers/bpfman-operator/tracepoint-program.go b/controllers/bpfman-operator/tracepoint-program.go index 758d509b0..908529b1f 100644 --- a/controllers/bpfman-operator/tracepoint-program.go +++ b/controllers/bpfman-operator/tracepoint-program.go @@ -11,6 +11,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanoperator import ( @@ -35,11 +37,11 @@ import ( //+kubebuilder:rbac:groups=bpfman.io,resources=tracepointprograms/finalizers,verbs=update type TracepointProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler } -func (r *TracepointProgramReconciler) getRecCommon() *ReconcilerCommon { - return &r.ReconcilerCommon +func (r *TracepointProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] { + return &r.ClusterProgramReconciler.ReconcilerCommon } func (r *TracepointProgramReconciler) getFinalizer() string { @@ -54,7 +56,7 @@ func (r *TracepointProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &bpfmaniov1alpha1.BpfProgram{}, &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.Tracepoint.String()))), + builder.WithPredicates(predicate.And(statusChangedPredicateCluster(), internal.BpfProgramTypePredicate(internal.Tracepoint.String()))), ). Complete(r) } @@ -101,7 +103,7 @@ func (r *TracepointProgramReconciler) Reconcile(ctx context.Context, req ctrl.Re return reconcileBpfProgram(ctx, r, tracepointProgram) } -func (r *TracepointProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +func (r *TracepointProgramReconciler) updateStatus(ctx context.Context, _namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { // Sometimes we end up with a stale TracepointProgram due to races, do this // get to ensure we're up to date before attempting a finalizer removal. prog := &bpfmaniov1alpha1.TracepointProgram{} diff --git a/controllers/bpfman-operator/tracepoint-program_test.go b/controllers/bpfman-operator/tracepoint-program_test.go index 8cac0a367..7b3e2756d 100644 --- a/controllers/bpfman-operator/tracepoint-program_test.go +++ b/controllers/bpfman-operator/tracepoint-program_test.go @@ -102,16 +102,20 @@ func TestTracepointProgramReconcile(t *testing.T) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithStatusSubresource(Tracepoint).WithRuntimeObjects(objs...).Build() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a TracepointProgram object with the scheme and fake client. - r := &TracepointProgramReconciler{ReconcilerCommon: rc} + r := &TracepointProgramReconciler{ClusterProgramReconciler: cpr} // Mock request to simulate Reconcile() being called on an event for a // watched resource . diff --git a/controllers/bpfman-operator/uprobe-ns-program.go b/controllers/bpfman-operator/uprobe-ns-program.go new file mode 100644 index 000000000..2d3c232a2 --- /dev/null +++ b/controllers/bpfman-operator/uprobe-ns-program.go @@ -0,0 +1,133 @@ +/* +Copyright 2024. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanoperator + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman-operator/internal" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=uprobensprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,resources=uprobensprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,resources=uprobensprograms/finalizers,verbs=update +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=uprobensprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=uprobensprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=uprobensprograms/finalizers,verbs=update + +type UprobeNsProgramReconciler struct { + NamespaceProgramReconciler +} + +func (r *UprobeNsProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList] { + return &r.NamespaceProgramReconciler.ReconcilerCommon +} + +func (r *UprobeNsProgramReconciler) getFinalizer() string { + return internal.UprobeNsProgramControllerFinalizer +} + +// SetupWithManager sets up the controller with the Manager. +func (r *UprobeNsProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.UprobeNsProgram{}). + // Watch BpfNsPrograms which are owned by UprobeNsPrograms + Watches( + &bpfmaniov1alpha1.BpfNsProgram{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates( + predicate.And(statusChangedPredicateNamespace(), + internal.BpfNsProgramTypePredicate(internal.UprobeString)), + ), + ). + Complete(r) +} + +func (r *UprobeNsProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Logger = ctrl.Log.WithName("uprobe-ns") + r.Logger.Info("bpfman-operator enter: uprobe-ns", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + + uprobeProgram := &bpfmaniov1alpha1.UprobeNsProgram{} + if err := r.Get(ctx, req.NamespacedName, uprobeProgram); err != nil { + // Reconcile was triggered by BpfNsProgram event, get parent UprobeNsProgram Object. + if errors.IsNotFound(err) { + bpfProgram := &bpfmaniov1alpha1.BpfNsProgram{} + if err := r.Get(ctx, req.NamespacedName, bpfProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.V(1).Info("BpfNsProgram not found stale reconcile, exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting BpfNsProgram Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + // Get owning UprobeNsProgram object from ownerRef + ownerRef := metav1.GetControllerOf(bpfProgram) + if ownerRef == nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting BpfNsProgram Object owner") + } + + if err := r.Get(ctx, types.NamespacedName{Namespace: req.NamespacedName.Namespace, Name: ownerRef.Name}, uprobeProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.Info("Uprobe Program from ownerRef not found stale reconcile exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting UprobeNsProgram Object from ownerRef", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + } else { + r.Logger.Error(err, "failed getting UprobeNsProgram Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + return ctrl.Result{}, nil + } + } + + return reconcileBpfProgram(ctx, r, uprobeProgram) +} + +func (r *UprobeNsProgramReconciler) updateStatus( + ctx context.Context, + namespace string, + name string, + cond bpfmaniov1alpha1.ProgramConditionType, + message string, +) (ctrl.Result, error) { + // Sometimes we end up with a stale UprobeNsProgram due to races, do this + // get to ensure we're up to date before attempting a status update. + prog := &bpfmaniov1alpha1.UprobeNsProgram{} + if err := r.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, prog); err != nil { + r.Logger.V(1).Info("failed to get fresh UprobeNsProgram object...requeuing") + return ctrl.Result{Requeue: true, RequeueAfter: retryDurationOperator}, nil + } + + return r.updateCondition(ctx, prog, &prog.Status.Conditions, cond, message) +} diff --git a/controllers/bpfman-operator/uprobe-ns-program_test.go b/controllers/bpfman-operator/uprobe-ns-program_test.go new file mode 100644 index 000000000..e99389125 --- /dev/null +++ b/controllers/bpfman-operator/uprobe-ns-program_test.go @@ -0,0 +1,171 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanoperator + +import ( + "context" + "fmt" + "testing" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + internal "github.com/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestUprobeNsProgramReconcile(t *testing.T) { + var ( + name = "fakeUprobeProgram" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + fakeNode = testutils.NewNode("fake-control-plane") + functionName = "malloc" + target = "libc" + offset = 0 + retprobe = false + ctx = context.TODO() + bpfProgName = fmt.Sprintf("%s-%s", name, fakeNode.Name) + ) + // A UprobeNsProgram object with metadata and spec. + Uprobe := &bpfmaniov1alpha1.UprobeNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: bpfmaniov1alpha1.UprobeNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + UprobeNsProgramInfo: bpfmaniov1alpha1.UprobeNsProgramInfo{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + FunctionName: functionName, + Target: target, + Offset: uint64(offset), + RetProbe: retprobe, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + }, + }, + } + + // The expected accompanying BpfNsProgram object + expectedBpfProg := &bpfmaniov1alpha1.BpfNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: bpfProgName, + OwnerReferences: []metav1.OwnerReference{ + { + Name: Uprobe.Name, + Controller: &[]bool{true}[0], + }, + }, + Labels: map[string]string{internal.BpfProgramOwner: Uprobe.Name, internal.K8sHostLabel: fakeNode.Name}, + Finalizers: []string{internal.UprobeNsProgramControllerFinalizer}, + }, + Spec: bpfmaniov1alpha1.BpfProgramSpec{ + Type: "uprobe", + }, + Status: bpfmaniov1alpha1.BpfProgramStatus{ + Conditions: []metav1.Condition{bpfmaniov1alpha1.BpfProgCondLoaded.Condition()}, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, Uprobe, expectedBpfProg} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, Uprobe) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(Uprobe).WithRuntimeObjects(objs...).Build() + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + } + + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a UprobeNsProgram object with the scheme and fake client. + r := &UprobeNsProgramReconciler{NamespaceProgramReconciler: npr} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + }, + } + + // First reconcile should add the finalzier to the UprobeNsProgram object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: Uprobe.Name, Namespace: Uprobe.Namespace}, Uprobe) + require.NoError(t, err) + + // Check the bpfman-operator finalizer was successfully added + require.Contains(t, Uprobe.GetFinalizers(), internal.BpfmanOperatorFinalizer) + + // Second reconcile should check BpfNsProgram Status and write Success condition to tcProgram Status + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: Uprobe.Name, Namespace: Uprobe.Namespace}, Uprobe) + require.NoError(t, err) + + require.Equal(t, Uprobe.Status.Conditions[0].Type, string(bpfmaniov1alpha1.ProgramReconcileSuccess)) + +} diff --git a/controllers/bpfman-operator/uprobe-program.go b/controllers/bpfman-operator/uprobe-program.go index dd02d3643..cdc4e571a 100644 --- a/controllers/bpfman-operator/uprobe-program.go +++ b/controllers/bpfman-operator/uprobe-program.go @@ -11,6 +11,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanoperator import ( @@ -35,11 +37,11 @@ import ( //+kubebuilder:rbac:groups=bpfman.io,resources=uprobeprograms/finalizers,verbs=update type UprobeProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler } -func (r *UprobeProgramReconciler) getRecCommon() *ReconcilerCommon { - return &r.ReconcilerCommon +func (r *UprobeProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] { + return &r.ClusterProgramReconciler.ReconcilerCommon } func (r *UprobeProgramReconciler) getFinalizer() string { @@ -54,7 +56,7 @@ func (r *UprobeProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &bpfmaniov1alpha1.BpfProgram{}, &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.UprobeString))), + builder.WithPredicates(predicate.And(statusChangedPredicateCluster(), internal.BpfProgramTypePredicate(internal.UprobeString))), ). Complete(r) } @@ -101,7 +103,7 @@ func (r *UprobeProgramReconciler) Reconcile(ctx context.Context, req ctrl.Reques return reconcileBpfProgram(ctx, r, uprobeProgram) } -func (r *UprobeProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +func (r *UprobeProgramReconciler) updateStatus(ctx context.Context, _namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { // Sometimes we end up with a stale UprobeProgram due to races, do this // get to ensure we're up to date before attempting a status update. prog := &bpfmaniov1alpha1.UprobeProgram{} diff --git a/controllers/bpfman-operator/uprobe-program_test.go b/controllers/bpfman-operator/uprobe-program_test.go index d591165fd..cb13972e7 100644 --- a/controllers/bpfman-operator/uprobe-program_test.go +++ b/controllers/bpfman-operator/uprobe-program_test.go @@ -108,16 +108,20 @@ func TestUprobeProgramReconcile(t *testing.T) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithStatusSubresource(Uprobe).WithRuntimeObjects(objs...).Build() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a UprobeProgram object with the scheme and fake client. - r := &UprobeProgramReconciler{ReconcilerCommon: rc} + r := &UprobeProgramReconciler{ClusterProgramReconciler: cpr} // Mock request to simulate Reconcile() being called on an event for a // watched resource . diff --git a/controllers/bpfman-operator/xdp-ns-program.go b/controllers/bpfman-operator/xdp-ns-program.go new file mode 100644 index 000000000..16b0a0d26 --- /dev/null +++ b/controllers/bpfman-operator/xdp-ns-program.go @@ -0,0 +1,135 @@ +/* +Copyright 2024. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + +package bpfmanoperator + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman-operator/internal" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=xdpnsprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,resources=xdpnsprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,resources=xdpnsprograms/finalizers,verbs=update +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=xdpnsprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=xdpnsprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,namespace=bpfman,resources=xdpnsprograms/finalizers,verbs=update + +type XdpNsProgramReconciler struct { + NamespaceProgramReconciler +} + +func (r *XdpNsProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList] { + return &r.NamespaceProgramReconciler.ReconcilerCommon +} + +func (r *XdpNsProgramReconciler) getFinalizer() string { + return internal.XdpNsProgramControllerFinalizer +} + +// SetupWithManager sets up the controller with the Manager. +func (r *XdpNsProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.XdpNsProgram{}). + // Watch bpfPrograms which are owned by XdpNsPrograms + Watches( + &bpfmaniov1alpha1.BpfNsProgram{}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And( + statusChangedPredicateNamespace(), + internal.BpfNsProgramTypePredicate(internal.Xdp.String())), + ), + ). + Complete(r) +} + +func (r *XdpNsProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Logger = ctrl.Log.WithName("xdp-ns") + r.Logger.Info("bpfman-operator enter: xdp-ns", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + + xdpNsProgram := &bpfmaniov1alpha1.XdpNsProgram{} + if err := r.Get(ctx, req.NamespacedName, xdpNsProgram); err != nil { + // list all XdpNsProgram objects with + if errors.IsNotFound(err) { + // TODO(astoycos) we could simplify this logic by making the name of the + // generated bpfProgram object a bit more deterministic + bpfProgram := &bpfmaniov1alpha1.BpfNsProgram{} + if err := r.Get(ctx, req.NamespacedName, bpfProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.V(1).Info("bpfProgram not found stale reconcile, exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting bpfProgram Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + // Get owning XdpNsProgram object from ownerRef + ownerRef := metav1.GetControllerOf(bpfProgram) + if ownerRef == nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfProgram Object owner") + } + + if err := r.Get(ctx, types.NamespacedName{Namespace: req.NamespacedName.Namespace, Name: ownerRef.Name}, xdpNsProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.Info("xdpNsProgram from ownerRef not found stale reconcile exiting", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } else { + r.Logger.Error(err, "failed getting XdpNsProgram Object from ownerRef", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + } + return ctrl.Result{}, nil + } + + } else { + r.Logger.Error(err, "failed getting XdpNsProgram Object", + "Namespace", req.NamespacedName.Namespace, "Name", req.NamespacedName.Name) + return ctrl.Result{}, nil + } + } + + return reconcileBpfProgram(ctx, r, xdpNsProgram) +} + +func (r *XdpNsProgramReconciler) updateStatus( + ctx context.Context, + namespace string, + name string, + cond bpfmaniov1alpha1.ProgramConditionType, + message string, +) (ctrl.Result, error) { + // Sometimes we end up with a stale XdpNsProgram due to races, do this + // get to ensure we're up to date before attempting a finalizer removal. + prog := &bpfmaniov1alpha1.XdpNsProgram{} + if err := r.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, prog); err != nil { + r.Logger.V(1).Error(err, "failed to get fresh XdpNsProgram object...requeuing") + return ctrl.Result{Requeue: true, RequeueAfter: retryDurationOperator}, nil + } + + return r.updateCondition(ctx, prog, &prog.Status.Conditions, cond, message) +} diff --git a/controllers/bpfman-operator/xdp-ns-program_test.go b/controllers/bpfman-operator/xdp-ns-program_test.go new file mode 100644 index 000000000..3f837cef2 --- /dev/null +++ b/controllers/bpfman-operator/xdp-ns-program_test.go @@ -0,0 +1,175 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanoperator + +import ( + "context" + "fmt" + "testing" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + internal "github.com/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman-operator/internal/test-utils" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestXdpNsProgramReconcile(t *testing.T) { + var ( + name = "fakeXdpNsProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + fakeNode = testutils.NewNode("fake-control-plane") + fakeInt = "eth0" + ctx = context.TODO() + bpfProgName = fmt.Sprintf("%s-%s", name, fakeNode.Name) + ) + // A XdpNsProgram object with metadata and spec. + Xdp := &bpfmaniov1alpha1.XdpNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: bpfmaniov1alpha1.XdpNsProgramSpec{ + BpfAppCommon: bpfmaniov1alpha1.BpfAppCommon{ + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + XdpNsProgramInfo: bpfmaniov1alpha1.XdpNsProgramInfo{ + + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + }, + InterfaceSelector: bpfmaniov1alpha1.InterfaceSelector{ + Interfaces: &[]string{fakeInt}, + }, + Priority: 0, + ProceedOn: []bpfmaniov1alpha1.XdpProceedOnValue{bpfmaniov1alpha1.XdpProceedOnValue("pass"), + bpfmaniov1alpha1.XdpProceedOnValue("dispatcher_return"), + }, + Containers: bpfmaniov1alpha1.ContainerNsSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + }, + }, + } + + // The expected accompanying BpfNsProgram object + expectedBpfProg := &bpfmaniov1alpha1.BpfNsProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: bpfProgName, + Namespace: namespace, + OwnerReferences: []metav1.OwnerReference{ + { + Name: Xdp.Name, + Controller: &[]bool{true}[0], + }, + }, + Labels: map[string]string{internal.BpfProgramOwner: Xdp.Name, internal.K8sHostLabel: fakeNode.Name}, + Finalizers: []string{internal.TcProgramControllerFinalizer}, + }, + Spec: bpfmaniov1alpha1.BpfProgramSpec{ + Type: "xdp", + }, + Status: bpfmaniov1alpha1.BpfProgramStatus{ + Conditions: []metav1.Condition{bpfmaniov1alpha1.BpfProgCondLoaded.Condition()}, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, Xdp, expectedBpfProg} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, Xdp) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfNsProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithStatusSubresource(Xdp).WithRuntimeObjects(objs...).Build() + + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfNsProgram, bpfmaniov1alpha1.BpfNsProgramList]{ + Client: cl, + Scheme: s, + } + + npr := NamespaceProgramReconciler{ + ReconcilerCommon: rc, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &XdpNsProgramReconciler{NamespaceProgramReconciler: npr} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should add the finalzier to the XdpNsProgram object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: Xdp.Name, Namespace: Xdp.Namespace}, Xdp) + require.NoError(t, err) + + // Check the bpfman-operator finalizer was successfully added + require.Contains(t, Xdp.GetFinalizers(), internal.BpfmanOperatorFinalizer) + + // Second reconcile should check BpfNsProgram Status and write Success condition to tcProgram Status + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfNsProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: Xdp.Name, Namespace: Xdp.Namespace}, Xdp) + require.NoError(t, err) + + require.Equal(t, Xdp.Status.Conditions[0].Type, string(bpfmaniov1alpha1.ProgramReconcileSuccess)) +} diff --git a/controllers/bpfman-operator/xdp-program.go b/controllers/bpfman-operator/xdp-program.go index c8603c588..f5493f373 100644 --- a/controllers/bpfman-operator/xdp-program.go +++ b/controllers/bpfman-operator/xdp-program.go @@ -11,6 +11,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +//lint:file-ignore U1000 Linter claims functions unused, but are required for generic + package bpfmanoperator import ( @@ -35,11 +37,11 @@ import ( //+kubebuilder:rbac:groups=bpfman.io,resources=xdpprograms/finalizers,verbs=update type XdpProgramReconciler struct { - ReconcilerCommon + ClusterProgramReconciler } -func (r *XdpProgramReconciler) getRecCommon() *ReconcilerCommon { - return &r.ReconcilerCommon +func (r *XdpProgramReconciler) getRecCommon() *ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList] { + return &r.ClusterProgramReconciler.ReconcilerCommon } func (r *XdpProgramReconciler) getFinalizer() string { @@ -54,7 +56,7 @@ func (r *XdpProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &bpfmaniov1alpha1.BpfProgram{}, &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.Xdp.String()))), + builder.WithPredicates(predicate.And(statusChangedPredicateCluster(), internal.BpfProgramTypePredicate(internal.Xdp.String()))), ). Complete(r) } @@ -103,7 +105,7 @@ func (r *XdpProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) return reconcileBpfProgram(ctx, r, xdpProgram) } -func (r *XdpProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { +func (r *XdpProgramReconciler) updateStatus(ctx context.Context, _namespace string, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { // Sometimes we end up with a stale XdpProgram due to races, do this // get to ensure we're up to date before attempting a finalizer removal. prog := &bpfmaniov1alpha1.XdpProgram{} diff --git a/controllers/bpfman-operator/xdp-program_test.go b/controllers/bpfman-operator/xdp-program_test.go index 92223a008..53aaa5457 100644 --- a/controllers/bpfman-operator/xdp-program_test.go +++ b/controllers/bpfman-operator/xdp-program_test.go @@ -89,7 +89,7 @@ func TestXdpProgramReconcile(t *testing.T) { Finalizers: []string{internal.TcProgramControllerFinalizer}, }, Spec: bpfmaniov1alpha1.BpfProgramSpec{ - Type: "tc", + Type: "xdp", }, Status: bpfmaniov1alpha1.BpfProgramStatus{ Conditions: []metav1.Condition{bpfmaniov1alpha1.BpfProgCondLoaded.Condition()}, @@ -108,16 +108,20 @@ func TestXdpProgramReconcile(t *testing.T) { // Create a fake client to mock API calls. cl := fake.NewClientBuilder().WithStatusSubresource(Xdp).WithRuntimeObjects(objs...).Build() - rc := ReconcilerCommon{ + rc := ReconcilerCommon[bpfmaniov1alpha1.BpfProgram, bpfmaniov1alpha1.BpfProgramList]{ Client: cl, Scheme: s, } + cpr := ClusterProgramReconciler{ + ReconcilerCommon: rc, + } + // Set development Logger so we can see all logs in tests. logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) // Create a ReconcileMemcached object with the scheme and fake client. - r := &XdpProgramReconciler{ReconcilerCommon: rc} + r := &XdpProgramReconciler{ClusterProgramReconciler: cpr} // Mock request to simulate Reconcile() being called on an event for a // watched resource . @@ -157,5 +161,4 @@ func TestXdpProgramReconcile(t *testing.T) { require.NoError(t, err) require.Equal(t, Xdp.Status.Conditions[0].Type, string(bpfmaniov1alpha1.ProgramReconcileSuccess)) - } diff --git a/hack/namespace_scoped.sh b/hack/namespace_scoped.sh new file mode 100755 index 000000000..34554668b --- /dev/null +++ b/hack/namespace_scoped.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash + +set -o errexit + +# This script generates the contents for a kubeconfig file for a given ServiceAccount. + +# Default the control variables, but they can be overwritten by passing in environment variables. +CLUSTER_NAME=${CLUSTER_NAME:-bpfman-deployment} +NAMESPACE=${NAMESPACE:-acme} +SERVICE_ACCOUNT=${SERVICE_ACCOUNT:-test-account} +SECRET_NAME=${SECRET_NAME:-test-account-token} + +# If a file is passed in, try to initialize the control variables from data in the file. +if [ ! -z "$1" ] ; then + if [ -f $1 ]; then + # For each variable below, pull the `kind: Secret` object from the file, which are the contents + # between "kind: Secret" and a possible "---". Then pipe that output into a `grep` to find + # individual fields. Then us `awk` to pull put the value. + + # Get NAMESPACE + tmpVar=$(awk '/kind: Secret/,/---/' $1 | grep -m 1 " namespace:" | awk -F' ' '{print $2}') + if [ ! -z "$tmpVar" ] ; then + NAMESPACE=${tmpVar} + fi + + # Get SERVICE_ACCOUNT + tmpVar=$(awk '/kind: Secret/,/---/' $1 | grep -m 1 " kubernetes.io/service-account.name:" | awk -F' ' '{print $2}') + if [ ! -z "$tmpVar" ] ; then + SERVICE_ACCOUNT=${tmpVar} + fi + + # Get SECRET_NAME + tmpVar=$(awk '/kind: Secret/,/---/' $1 | grep -m 1 " name:" | awk -F' ' '{print $2}') + if [ ! -z "$tmpVar" ] ; then + SECRET_NAME=${tmpVar} + fi + fi +fi + +# Determine the server IP Address using `kubectl cluster-info`. +# The first line of the output looks like: +# "Kubernetes control plane is running at https://127.0.0.1:35841" +# The `grep` command gets the first instance of the "https" and returns that line. +# The 'awk' command pulls out the 7th word in the sentence, the "https:" part. +# The `sed` command strips off some color control characters that are include in the output. +server=$(kubectl cluster-info | grep -m 1 "https" | awk -F' ' '{print $7}' | sed 's/\x1b\[[0-9;]*m//g') + +# Get the secret data. +ca=$(kubectl --namespace="${NAMESPACE}" get secret/"${SECRET_NAME}" -o=jsonpath='{.data.ca\.crt}') +token=$(kubectl --namespace="${NAMESPACE}" get secret/"${SECRET_NAME}" -o=jsonpath='{.data.token}' | base64 --decode) + +# Generate a kubeconfig based on gathered data. +echo "apiVersion: v1 +kind: Config +clusters: + - name: ${CLUSTER_NAME} + cluster: + certificate-authority-data: ${ca} + server: ${server} +contexts: + - name: ${SERVICE_ACCOUNT}@${CLUSTER_NAME} + context: + cluster: ${CLUSTER_NAME} + namespace: ${NAMESPACE} + user: ${SERVICE_ACCOUNT} +users: + - name: ${SERVICE_ACCOUNT} + user: + token: ${token} +current-context: ${SERVICE_ACCOUNT}@${CLUSTER_NAME} +" diff --git a/hack/namespace_scoped.yaml b/hack/namespace_scoped.yaml new file mode 100644 index 000000000..3e48015ea --- /dev/null +++ b/hack/namespace_scoped.yaml @@ -0,0 +1,76 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: acme +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: test-account + namespace: acme +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: test-account + namespace: acme +rules: +- apiGroups: ['', 'extensions', 'apps'] + resources: ['*'] + verbs: ['*'] +- apiGroups: ['batch'] + resources: + - jobs + - cronjobs + verbs: ['*'] +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - bpfnsprograms/status + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + # resources: ['xdpnsprograms'] + resources: ['bpfnsapplications', 'tcnsprograms', 'tcxnsprograms', 'uprobensprograms', 'xdpnsprograms'] + verbs: + - create + - delete + - get + - list + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: test-account + namespace: acme +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: test-account +subjects: + - kind: ServiceAccount + name: test-account + namespace: acme +--- +apiVersion: v1 +kind: Secret +metadata: + name: test-account-token + namespace: acme + annotations: + kubernetes.io/service-account.name: test-account +type: kubernetes.io/service-account-token diff --git a/hack/nginx-deployment.yaml b/hack/nginx-deployment.yaml index 656ddc322..330fb2c14 100644 --- a/hack/nginx-deployment.yaml +++ b/hack/nginx-deployment.yaml @@ -2,6 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment + namespace: acme spec: selector: matchLabels: diff --git a/internal/constants.go b/internal/constants.go index 3c324dde8..8ccf208e4 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -19,43 +19,59 @@ package internal import "fmt" const ( - XdpProgramInterface = "bpfman.io.xdpprogramcontroller/interface" - XdpContainerPid = "bpfman.io.xdpprogramcontroller/containerpid" - XdpNoContainersOnNode = "bpfman.io.xdpprogramcontroller/nocontainersonnode" - TcProgramInterface = "bpfman.io.tcprogramcontroller/interface" - TcContainerPid = "bpfman.io.tcprogramcontroller/containerpid" - TcNoContainersOnNode = "bpfman.io.tcprogramcontroller/nocontainersonnode" - TcxProgramInterface = "bpfman.io.tcxprogramcontroller/interface" - TcxContainerPid = "bpfman.io.tcxprogramcontroller/containerpid" - TcxNoContainersOnNode = "bpfman.io.tcxprogramcontroller/nocontainersonnode" - TracepointProgramTracepoint = "bpfman.io.tracepointprogramcontroller/tracepoint" - KprobeProgramFunction = "bpfman.io.kprobeprogramcontroller/function" - UprobeProgramTarget = "bpfman.io.uprobeprogramcontroller/target" - UprobeContainerPid = "bpfman.io.uprobeprogramcontroller/containerpid" - UprobeNoContainersOnNode = "bpfman.io.uprobeprogramcontroller/nocontainersonnode" - FentryProgramFunction = "bpfman.io.fentryprogramcontroller/function" - FexitProgramFunction = "bpfman.io.fexitprogramcontroller/function" - K8sHostLabel = "kubernetes.io/hostname" - DiscoveredLabel = "bpfman.io/discoveredProgram" - IdAnnotation = "bpfman.io/ProgramId" - UuidMetadataKey = "bpfman.io/uuid" - ProgramNameKey = "bpfman.io/ProgramName" - BpfmanNs = "bpfman" - BpfmanOperatorName = "bpfman-operator" - BpfmanDsName = "bpfman-daemon" - BpfmanConfigName = "bpfman-config" - BpfmanCsiDriverName = "csi.bpfman.io" - BpfmanRestrictedSccName = "bpfman-restricted" - BpfmanContainerName = "bpfman" - BpfmanAgentContainerName = "bpfman-agent" - BpfmanDaemonManifestPath = "./config/bpfman-deployment/daemonset.yaml" - BpfmanCsiDriverPath = "./config/bpfman-deployment/csidriverinfo.yaml" - BpfmanRestrictedSCCPath = "./config/openshift/restricted-scc.yaml" - BpfmanMapFs = "/run/bpfman/fs/maps" - DefaultType = "tcp" - DefaultPath = "/run/bpfman-sock/bpfman.sock" - DefaultPort = 50051 - DefaultEnabled = true + FentryProgramFunction = "bpfman.io.fentryprogramcontroller/function" + FentryNoContainersOnNode = "bpfman.io.fentryprogramcontroller/nocontainersonnode" + FexitProgramFunction = "bpfman.io.fexitprogramcontroller/function" + FexitNoContainersOnNode = "bpfman.io.fexitprogramcontroller/nocontainersonnode" + KprobeProgramFunction = "bpfman.io.kprobeprogramcontroller/function" + KprobeNoContainersOnNode = "bpfman.io.kprobeprogramcontroller/nocontainersonnode" + TcProgramInterface = "bpfman.io.tcprogramcontroller/interface" + TcContainerPid = "bpfman.io.tcprogramcontroller/containerpid" + TcNoContainersOnNode = "bpfman.io.tcprogramcontroller/nocontainersonnode" + TcNsProgramInterface = "bpfman.io.tcnsprogramcontroller/interface" + TcNsContainerPid = "bpfman.io.tcnsprogramcontroller/containerpid" + TcNsNoContainersOnNode = "bpfman.io.tcnsprogramcontroller/nocontainersonnode" + TcxProgramInterface = "bpfman.io.tcxprogramcontroller/interface" + TcxContainerPid = "bpfman.io.tcxprogramcontroller/containerpid" + TcxNoContainersOnNode = "bpfman.io.tcxprogramcontroller/nocontainersonnode" + TcxNsProgramInterface = "bpfman.io.tcxnsprogramcontroller/interface" + TcxNsContainerPid = "bpfman.io.tcxnsprogramcontroller/containerpid" + TcxNsNoContainersOnNode = "bpfman.io.tcxnsprogramcontroller/nocontainersonnode" + TracepointProgramTracepoint = "bpfman.io.tracepointprogramcontroller/tracepoint" + TracepointNoContainersOnNode = "bpfman.io.tracepointprogramcontroller/nocontainersonnode" + UprobeProgramTarget = "bpfman.io.uprobeprogramcontroller/target" + UprobeContainerPid = "bpfman.io.uprobeprogramcontroller/containerpid" + UprobeNoContainersOnNode = "bpfman.io.uprobeprogramcontroller/nocontainersonnode" + UprobeNsProgramTarget = "bpfman.io.uprobensprogramcontroller/target" + UprobeNsContainerPid = "bpfman.io.uprobensprogramcontroller/containerpid" + UprobeNsNoContainersOnNode = "bpfman.io.uprobensprogramcontroller/nocontainersonnode" + XdpProgramInterface = "bpfman.io.xdpprogramcontroller/interface" + XdpContainerPid = "bpfman.io.xdpprogramcontroller/containerpid" + XdpNoContainersOnNode = "bpfman.io.xdpprogramcontroller/nocontainersonnode" + XdpNsProgramInterface = "bpfman.io.xdpnsprogramcontroller/interface" + XdpNsContainerPid = "bpfman.io.xdpnsprogramcontroller/containerpid" + XdpNsNoContainersOnNode = "bpfman.io.xdpnsprogramcontroller/nocontainersonnode" + K8sHostLabel = "kubernetes.io/hostname" + DiscoveredLabel = "bpfman.io/discoveredProgram" + IdAnnotation = "bpfman.io/ProgramId" + UuidMetadataKey = "bpfman.io/uuid" + ProgramNameKey = "bpfman.io/ProgramName" + BpfmanNs = "bpfman" + BpfmanOperatorName = "bpfman-operator" + BpfmanDsName = "bpfman-daemon" + BpfmanConfigName = "bpfman-config" + BpfmanCsiDriverName = "csi.bpfman.io" + BpfmanRestrictedSccName = "bpfman-restricted" + BpfmanContainerName = "bpfman" + BpfmanAgentContainerName = "bpfman-agent" + BpfmanDaemonManifestPath = "./config/bpfman-deployment/daemonset.yaml" + BpfmanCsiDriverPath = "./config/bpfman-deployment/csidriverinfo.yaml" + BpfmanRestrictedSCCPath = "./config/openshift/restricted-scc.yaml" + BpfmanMapFs = "/run/bpfman/fs/maps" + DefaultType = "tcp" + DefaultPath = "/run/bpfman-sock/bpfman.sock" + DefaultPort = 50051 + DefaultEnabled = true // BpfProgramOwner is the name of the object that owns the BpfProgram // object. In the case of a *Program, it will be the name of the *Program // object. In the case of a BpfApplication, it will be the name of the @@ -80,12 +96,21 @@ const ( // XdpProgramControllerFinalizer is the finalizer that holds an Xdp BpfProgram // object from deletion until cleanup can be performed. XdpProgramControllerFinalizer = "bpfman.io.xdpprogramcontroller/finalizer" + // XdpNsProgramControllerFinalizer is the finalizer that holds a Namespaced Xdp BpfProgram + // object from deletion until cleanup can be performed. + XdpNsProgramControllerFinalizer = "bpfman.io.xdpnsprogramcontroller/finalizer" // TcProgramControllerFinalizer is the finalizer that holds an Tc BpfProgram // object from deletion until cleanup can be performed. TcProgramControllerFinalizer = "bpfman.io.tcprogramcontroller/finalizer" + // TcNsProgramControllerFinalizer is the finalizer that holds a Namespaced Tc BpfProgram + // object from deletion until cleanup can be performed. + TcNsProgramControllerFinalizer = "bpfman.io.tcnsprogramcontroller/finalizer" // TcxProgramControllerFinalizer is the finalizer that holds an Tcx BpfProgram // object from deletion until cleanup can be performed. TcxProgramControllerFinalizer = "bpfman.io.tcxprogramcontroller/finalizer" + // TcxNsProgramControllerFinalizer is the finalizer that holds a Namespaced Tcx BpfProgram + // object from deletion until cleanup can be performed. + TcxNsProgramControllerFinalizer = "bpfman.io.tcxnsprogramcontroller/finalizer" // TracepointProgramControllerFinalizer is the finalizer that holds an Tracepoint // BpfProgram object from deletion until cleanup can be performed. TracepointProgramControllerFinalizer = "bpfman.io.tracepointprogramcontroller/finalizer" @@ -95,6 +120,9 @@ const ( // UprobeProgramControllerFinalizer is the finalizer that holds a Uprobe // BpfProgram object from deletion until cleanup can be performed. UprobeProgramControllerFinalizer = "bpfman.io.uprobeprogramcontroller/finalizer" + // UprobeNsProgramControllerFinalizer is the finalizer that holds a Namespaced Uprobe + // BpfProgram object from deletion until cleanup can be performed. + UprobeNsProgramControllerFinalizer = "bpfman.io.uprobensprogramcontroller/finalizer" // FentryProgramControllerFinalizer is the finalizer that holds a Fentry // BpfProgram object from deletion until cleanup can be performed. FentryProgramControllerFinalizer = "bpfman.io.fentryprogramcontroller/finalizer" @@ -103,6 +131,8 @@ const ( FexitProgramControllerFinalizer = "bpfman.io.fexitprogramcontroller/finalizer" // BpfApplicationFinalizer is the finalizer that holds a BpfApplication BpfApplicationControllerFinalizer = "bpfman.io.bpfapplicationcontroller/finalizer" + // BpfApplicationFinalizer is the finalizer that holds a BpfApplication + BpfNsApplicationControllerFinalizer = "bpfman.io.bpfnsapplicationcontroller/finalizer" ) // Must match the kernel's `bpf_prog_type` enum. diff --git a/internal/k8s.go b/internal/k8s.go index 2945b14f9..174178448 100644 --- a/internal/k8s.go +++ b/internal/k8s.go @@ -43,6 +43,24 @@ func BpfProgramTypePredicate(kind string) predicate.Funcs { } } +// Only reconcile if a bpfnsprogram has been created for the controller's program type. +func BpfNsProgramTypePredicate(kind string) predicate.Funcs { + return predicate.Funcs{ + GenericFunc: func(e event.GenericEvent) bool { + return e.Object.(*bpfmaniov1alpha1.BpfNsProgram).Spec.Type == kind + }, + CreateFunc: func(e event.CreateEvent) bool { + return e.Object.(*bpfmaniov1alpha1.BpfNsProgram).Spec.Type == kind + }, + UpdateFunc: func(e event.UpdateEvent) bool { + return e.ObjectNew.(*bpfmaniov1alpha1.BpfNsProgram).Spec.Type == kind + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return e.Object.(*bpfmaniov1alpha1.BpfNsProgram).Spec.Type == kind + }, + } +} + // Only reconcile if a bpfprogram has been created for a controller's node. func BpfProgramNodePredicate(nodeName string) predicate.Funcs { return predicate.Funcs{ diff --git a/pkg/client/apis/v1alpha1/bpfnsapplication.go b/pkg/client/apis/v1alpha1/bpfnsapplication.go new file mode 100644 index 000000000..ce117b0e7 --- /dev/null +++ b/pkg/client/apis/v1alpha1/bpfnsapplication.go @@ -0,0 +1,99 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// BpfNsApplicationLister helps list BpfNsApplications. +// All objects returned here must be treated as read-only. +type BpfNsApplicationLister interface { + // List lists all BpfNsApplications in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.BpfNsApplication, err error) + // BpfNsApplications returns an object that can list and get BpfNsApplications. + BpfNsApplications(namespace string) BpfNsApplicationNamespaceLister + BpfNsApplicationListerExpansion +} + +// bpfNsApplicationLister implements the BpfNsApplicationLister interface. +type bpfNsApplicationLister struct { + indexer cache.Indexer +} + +// NewBpfNsApplicationLister returns a new BpfNsApplicationLister. +func NewBpfNsApplicationLister(indexer cache.Indexer) BpfNsApplicationLister { + return &bpfNsApplicationLister{indexer: indexer} +} + +// List lists all BpfNsApplications in the indexer. +func (s *bpfNsApplicationLister) List(selector labels.Selector) (ret []*v1alpha1.BpfNsApplication, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.BpfNsApplication)) + }) + return ret, err +} + +// BpfNsApplications returns an object that can list and get BpfNsApplications. +func (s *bpfNsApplicationLister) BpfNsApplications(namespace string) BpfNsApplicationNamespaceLister { + return bpfNsApplicationNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// BpfNsApplicationNamespaceLister helps list and get BpfNsApplications. +// All objects returned here must be treated as read-only. +type BpfNsApplicationNamespaceLister interface { + // List lists all BpfNsApplications in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.BpfNsApplication, err error) + // Get retrieves the BpfNsApplication from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.BpfNsApplication, error) + BpfNsApplicationNamespaceListerExpansion +} + +// bpfNsApplicationNamespaceLister implements the BpfNsApplicationNamespaceLister +// interface. +type bpfNsApplicationNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all BpfNsApplications in the indexer for a given namespace. +func (s bpfNsApplicationNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.BpfNsApplication, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.BpfNsApplication)) + }) + return ret, err +} + +// Get retrieves the BpfNsApplication from the indexer for a given namespace and name. +func (s bpfNsApplicationNamespaceLister) Get(name string) (*v1alpha1.BpfNsApplication, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("bpfnsapplication"), name) + } + return obj.(*v1alpha1.BpfNsApplication), nil +} diff --git a/pkg/client/apis/v1alpha1/bpfnsprogram.go b/pkg/client/apis/v1alpha1/bpfnsprogram.go new file mode 100644 index 000000000..4846a24fb --- /dev/null +++ b/pkg/client/apis/v1alpha1/bpfnsprogram.go @@ -0,0 +1,99 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// BpfNsProgramLister helps list BpfNsPrograms. +// All objects returned here must be treated as read-only. +type BpfNsProgramLister interface { + // List lists all BpfNsPrograms in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.BpfNsProgram, err error) + // BpfNsPrograms returns an object that can list and get BpfNsPrograms. + BpfNsPrograms(namespace string) BpfNsProgramNamespaceLister + BpfNsProgramListerExpansion +} + +// bpfNsProgramLister implements the BpfNsProgramLister interface. +type bpfNsProgramLister struct { + indexer cache.Indexer +} + +// NewBpfNsProgramLister returns a new BpfNsProgramLister. +func NewBpfNsProgramLister(indexer cache.Indexer) BpfNsProgramLister { + return &bpfNsProgramLister{indexer: indexer} +} + +// List lists all BpfNsPrograms in the indexer. +func (s *bpfNsProgramLister) List(selector labels.Selector) (ret []*v1alpha1.BpfNsProgram, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.BpfNsProgram)) + }) + return ret, err +} + +// BpfNsPrograms returns an object that can list and get BpfNsPrograms. +func (s *bpfNsProgramLister) BpfNsPrograms(namespace string) BpfNsProgramNamespaceLister { + return bpfNsProgramNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// BpfNsProgramNamespaceLister helps list and get BpfNsPrograms. +// All objects returned here must be treated as read-only. +type BpfNsProgramNamespaceLister interface { + // List lists all BpfNsPrograms in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.BpfNsProgram, err error) + // Get retrieves the BpfNsProgram from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.BpfNsProgram, error) + BpfNsProgramNamespaceListerExpansion +} + +// bpfNsProgramNamespaceLister implements the BpfNsProgramNamespaceLister +// interface. +type bpfNsProgramNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all BpfNsPrograms in the indexer for a given namespace. +func (s bpfNsProgramNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.BpfNsProgram, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.BpfNsProgram)) + }) + return ret, err +} + +// Get retrieves the BpfNsProgram from the indexer for a given namespace and name. +func (s bpfNsProgramNamespaceLister) Get(name string) (*v1alpha1.BpfNsProgram, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("bpfnsprogram"), name) + } + return obj.(*v1alpha1.BpfNsProgram), nil +} diff --git a/pkg/client/apis/v1alpha1/expansion_generated.go b/pkg/client/apis/v1alpha1/expansion_generated.go index df6e451bd..491e889c5 100644 --- a/pkg/client/apis/v1alpha1/expansion_generated.go +++ b/pkg/client/apis/v1alpha1/expansion_generated.go @@ -22,6 +22,22 @@ package v1alpha1 // BpfApplicationLister. type BpfApplicationListerExpansion interface{} +// BpfNsApplicationListerExpansion allows custom methods to be added to +// BpfNsApplicationLister. +type BpfNsApplicationListerExpansion interface{} + +// BpfNsApplicationNamespaceListerExpansion allows custom methods to be added to +// BpfNsApplicationNamespaceLister. +type BpfNsApplicationNamespaceListerExpansion interface{} + +// BpfNsProgramListerExpansion allows custom methods to be added to +// BpfNsProgramLister. +type BpfNsProgramListerExpansion interface{} + +// BpfNsProgramNamespaceListerExpansion allows custom methods to be added to +// BpfNsProgramNamespaceLister. +type BpfNsProgramNamespaceListerExpansion interface{} + // BpfProgramListerExpansion allows custom methods to be added to // BpfProgramLister. type BpfProgramListerExpansion interface{} @@ -38,10 +54,26 @@ type FexitProgramListerExpansion interface{} // KprobeProgramLister. type KprobeProgramListerExpansion interface{} +// TcNsProgramListerExpansion allows custom methods to be added to +// TcNsProgramLister. +type TcNsProgramListerExpansion interface{} + +// TcNsProgramNamespaceListerExpansion allows custom methods to be added to +// TcNsProgramNamespaceLister. +type TcNsProgramNamespaceListerExpansion interface{} + // TcProgramListerExpansion allows custom methods to be added to // TcProgramLister. type TcProgramListerExpansion interface{} +// TcxNsProgramListerExpansion allows custom methods to be added to +// TcxNsProgramLister. +type TcxNsProgramListerExpansion interface{} + +// TcxNsProgramNamespaceListerExpansion allows custom methods to be added to +// TcxNsProgramNamespaceLister. +type TcxNsProgramNamespaceListerExpansion interface{} + // TcxProgramListerExpansion allows custom methods to be added to // TcxProgramLister. type TcxProgramListerExpansion interface{} @@ -50,10 +82,26 @@ type TcxProgramListerExpansion interface{} // TracepointProgramLister. type TracepointProgramListerExpansion interface{} +// UprobeNsProgramListerExpansion allows custom methods to be added to +// UprobeNsProgramLister. +type UprobeNsProgramListerExpansion interface{} + +// UprobeNsProgramNamespaceListerExpansion allows custom methods to be added to +// UprobeNsProgramNamespaceLister. +type UprobeNsProgramNamespaceListerExpansion interface{} + // UprobeProgramListerExpansion allows custom methods to be added to // UprobeProgramLister. type UprobeProgramListerExpansion interface{} +// XdpNsProgramListerExpansion allows custom methods to be added to +// XdpNsProgramLister. +type XdpNsProgramListerExpansion interface{} + +// XdpNsProgramNamespaceListerExpansion allows custom methods to be added to +// XdpNsProgramNamespaceLister. +type XdpNsProgramNamespaceListerExpansion interface{} + // XdpProgramListerExpansion allows custom methods to be added to // XdpProgramLister. type XdpProgramListerExpansion interface{} diff --git a/pkg/client/apis/v1alpha1/tcnsprogram.go b/pkg/client/apis/v1alpha1/tcnsprogram.go new file mode 100644 index 000000000..3a4632bf1 --- /dev/null +++ b/pkg/client/apis/v1alpha1/tcnsprogram.go @@ -0,0 +1,99 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// TcNsProgramLister helps list TcNsPrograms. +// All objects returned here must be treated as read-only. +type TcNsProgramLister interface { + // List lists all TcNsPrograms in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.TcNsProgram, err error) + // TcNsPrograms returns an object that can list and get TcNsPrograms. + TcNsPrograms(namespace string) TcNsProgramNamespaceLister + TcNsProgramListerExpansion +} + +// tcNsProgramLister implements the TcNsProgramLister interface. +type tcNsProgramLister struct { + indexer cache.Indexer +} + +// NewTcNsProgramLister returns a new TcNsProgramLister. +func NewTcNsProgramLister(indexer cache.Indexer) TcNsProgramLister { + return &tcNsProgramLister{indexer: indexer} +} + +// List lists all TcNsPrograms in the indexer. +func (s *tcNsProgramLister) List(selector labels.Selector) (ret []*v1alpha1.TcNsProgram, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.TcNsProgram)) + }) + return ret, err +} + +// TcNsPrograms returns an object that can list and get TcNsPrograms. +func (s *tcNsProgramLister) TcNsPrograms(namespace string) TcNsProgramNamespaceLister { + return tcNsProgramNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// TcNsProgramNamespaceLister helps list and get TcNsPrograms. +// All objects returned here must be treated as read-only. +type TcNsProgramNamespaceLister interface { + // List lists all TcNsPrograms in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.TcNsProgram, err error) + // Get retrieves the TcNsProgram from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.TcNsProgram, error) + TcNsProgramNamespaceListerExpansion +} + +// tcNsProgramNamespaceLister implements the TcNsProgramNamespaceLister +// interface. +type tcNsProgramNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all TcNsPrograms in the indexer for a given namespace. +func (s tcNsProgramNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.TcNsProgram, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.TcNsProgram)) + }) + return ret, err +} + +// Get retrieves the TcNsProgram from the indexer for a given namespace and name. +func (s tcNsProgramNamespaceLister) Get(name string) (*v1alpha1.TcNsProgram, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("tcnsprogram"), name) + } + return obj.(*v1alpha1.TcNsProgram), nil +} diff --git a/pkg/client/apis/v1alpha1/tcxnsprogram.go b/pkg/client/apis/v1alpha1/tcxnsprogram.go new file mode 100644 index 000000000..399b3403d --- /dev/null +++ b/pkg/client/apis/v1alpha1/tcxnsprogram.go @@ -0,0 +1,99 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// TcxNsProgramLister helps list TcxNsPrograms. +// All objects returned here must be treated as read-only. +type TcxNsProgramLister interface { + // List lists all TcxNsPrograms in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.TcxNsProgram, err error) + // TcxNsPrograms returns an object that can list and get TcxNsPrograms. + TcxNsPrograms(namespace string) TcxNsProgramNamespaceLister + TcxNsProgramListerExpansion +} + +// tcxNsProgramLister implements the TcxNsProgramLister interface. +type tcxNsProgramLister struct { + indexer cache.Indexer +} + +// NewTcxNsProgramLister returns a new TcxNsProgramLister. +func NewTcxNsProgramLister(indexer cache.Indexer) TcxNsProgramLister { + return &tcxNsProgramLister{indexer: indexer} +} + +// List lists all TcxNsPrograms in the indexer. +func (s *tcxNsProgramLister) List(selector labels.Selector) (ret []*v1alpha1.TcxNsProgram, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.TcxNsProgram)) + }) + return ret, err +} + +// TcxNsPrograms returns an object that can list and get TcxNsPrograms. +func (s *tcxNsProgramLister) TcxNsPrograms(namespace string) TcxNsProgramNamespaceLister { + return tcxNsProgramNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// TcxNsProgramNamespaceLister helps list and get TcxNsPrograms. +// All objects returned here must be treated as read-only. +type TcxNsProgramNamespaceLister interface { + // List lists all TcxNsPrograms in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.TcxNsProgram, err error) + // Get retrieves the TcxNsProgram from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.TcxNsProgram, error) + TcxNsProgramNamespaceListerExpansion +} + +// tcxNsProgramNamespaceLister implements the TcxNsProgramNamespaceLister +// interface. +type tcxNsProgramNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all TcxNsPrograms in the indexer for a given namespace. +func (s tcxNsProgramNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.TcxNsProgram, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.TcxNsProgram)) + }) + return ret, err +} + +// Get retrieves the TcxNsProgram from the indexer for a given namespace and name. +func (s tcxNsProgramNamespaceLister) Get(name string) (*v1alpha1.TcxNsProgram, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("tcxnsprogram"), name) + } + return obj.(*v1alpha1.TcxNsProgram), nil +} diff --git a/pkg/client/apis/v1alpha1/uprobensprogram.go b/pkg/client/apis/v1alpha1/uprobensprogram.go new file mode 100644 index 000000000..927061ec0 --- /dev/null +++ b/pkg/client/apis/v1alpha1/uprobensprogram.go @@ -0,0 +1,99 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// UprobeNsProgramLister helps list UprobeNsPrograms. +// All objects returned here must be treated as read-only. +type UprobeNsProgramLister interface { + // List lists all UprobeNsPrograms in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.UprobeNsProgram, err error) + // UprobeNsPrograms returns an object that can list and get UprobeNsPrograms. + UprobeNsPrograms(namespace string) UprobeNsProgramNamespaceLister + UprobeNsProgramListerExpansion +} + +// uprobeNsProgramLister implements the UprobeNsProgramLister interface. +type uprobeNsProgramLister struct { + indexer cache.Indexer +} + +// NewUprobeNsProgramLister returns a new UprobeNsProgramLister. +func NewUprobeNsProgramLister(indexer cache.Indexer) UprobeNsProgramLister { + return &uprobeNsProgramLister{indexer: indexer} +} + +// List lists all UprobeNsPrograms in the indexer. +func (s *uprobeNsProgramLister) List(selector labels.Selector) (ret []*v1alpha1.UprobeNsProgram, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.UprobeNsProgram)) + }) + return ret, err +} + +// UprobeNsPrograms returns an object that can list and get UprobeNsPrograms. +func (s *uprobeNsProgramLister) UprobeNsPrograms(namespace string) UprobeNsProgramNamespaceLister { + return uprobeNsProgramNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// UprobeNsProgramNamespaceLister helps list and get UprobeNsPrograms. +// All objects returned here must be treated as read-only. +type UprobeNsProgramNamespaceLister interface { + // List lists all UprobeNsPrograms in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.UprobeNsProgram, err error) + // Get retrieves the UprobeNsProgram from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.UprobeNsProgram, error) + UprobeNsProgramNamespaceListerExpansion +} + +// uprobeNsProgramNamespaceLister implements the UprobeNsProgramNamespaceLister +// interface. +type uprobeNsProgramNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all UprobeNsPrograms in the indexer for a given namespace. +func (s uprobeNsProgramNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.UprobeNsProgram, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.UprobeNsProgram)) + }) + return ret, err +} + +// Get retrieves the UprobeNsProgram from the indexer for a given namespace and name. +func (s uprobeNsProgramNamespaceLister) Get(name string) (*v1alpha1.UprobeNsProgram, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("uprobensprogram"), name) + } + return obj.(*v1alpha1.UprobeNsProgram), nil +} diff --git a/pkg/client/apis/v1alpha1/xdpnsprogram.go b/pkg/client/apis/v1alpha1/xdpnsprogram.go new file mode 100644 index 000000000..ba698cd14 --- /dev/null +++ b/pkg/client/apis/v1alpha1/xdpnsprogram.go @@ -0,0 +1,99 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// XdpNsProgramLister helps list XdpNsPrograms. +// All objects returned here must be treated as read-only. +type XdpNsProgramLister interface { + // List lists all XdpNsPrograms in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.XdpNsProgram, err error) + // XdpNsPrograms returns an object that can list and get XdpNsPrograms. + XdpNsPrograms(namespace string) XdpNsProgramNamespaceLister + XdpNsProgramListerExpansion +} + +// xdpNsProgramLister implements the XdpNsProgramLister interface. +type xdpNsProgramLister struct { + indexer cache.Indexer +} + +// NewXdpNsProgramLister returns a new XdpNsProgramLister. +func NewXdpNsProgramLister(indexer cache.Indexer) XdpNsProgramLister { + return &xdpNsProgramLister{indexer: indexer} +} + +// List lists all XdpNsPrograms in the indexer. +func (s *xdpNsProgramLister) List(selector labels.Selector) (ret []*v1alpha1.XdpNsProgram, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.XdpNsProgram)) + }) + return ret, err +} + +// XdpNsPrograms returns an object that can list and get XdpNsPrograms. +func (s *xdpNsProgramLister) XdpNsPrograms(namespace string) XdpNsProgramNamespaceLister { + return xdpNsProgramNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// XdpNsProgramNamespaceLister helps list and get XdpNsPrograms. +// All objects returned here must be treated as read-only. +type XdpNsProgramNamespaceLister interface { + // List lists all XdpNsPrograms in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.XdpNsProgram, err error) + // Get retrieves the XdpNsProgram from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.XdpNsProgram, error) + XdpNsProgramNamespaceListerExpansion +} + +// xdpNsProgramNamespaceLister implements the XdpNsProgramNamespaceLister +// interface. +type xdpNsProgramNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all XdpNsPrograms in the indexer for a given namespace. +func (s xdpNsProgramNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.XdpNsProgram, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.XdpNsProgram)) + }) + return ret, err +} + +// Get retrieves the XdpNsProgram from the indexer for a given namespace and name. +func (s xdpNsProgramNamespaceLister) Get(name string) (*v1alpha1.XdpNsProgram, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("xdpnsprogram"), name) + } + return obj.(*v1alpha1.XdpNsProgram), nil +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/apis_client.go b/pkg/client/clientset/typed/apis/v1alpha1/apis_client.go index ac2e4d3a1..7ec7b412d 100644 --- a/pkg/client/clientset/typed/apis/v1alpha1/apis_client.go +++ b/pkg/client/clientset/typed/apis/v1alpha1/apis_client.go @@ -29,14 +29,20 @@ import ( type BpfmanV1alpha1Interface interface { RESTClient() rest.Interface BpfApplicationsGetter + BpfNsApplicationsGetter + BpfNsProgramsGetter BpfProgramsGetter FentryProgramsGetter FexitProgramsGetter KprobeProgramsGetter + TcNsProgramsGetter TcProgramsGetter + TcxNsProgramsGetter TcxProgramsGetter TracepointProgramsGetter + UprobeNsProgramsGetter UprobeProgramsGetter + XdpNsProgramsGetter XdpProgramsGetter } @@ -49,6 +55,14 @@ func (c *BpfmanV1alpha1Client) BpfApplications() BpfApplicationInterface { return newBpfApplications(c) } +func (c *BpfmanV1alpha1Client) BpfNsApplications(namespace string) BpfNsApplicationInterface { + return newBpfNsApplications(c, namespace) +} + +func (c *BpfmanV1alpha1Client) BpfNsPrograms(namespace string) BpfNsProgramInterface { + return newBpfNsPrograms(c, namespace) +} + func (c *BpfmanV1alpha1Client) BpfPrograms() BpfProgramInterface { return newBpfPrograms(c) } @@ -65,10 +79,18 @@ func (c *BpfmanV1alpha1Client) KprobePrograms() KprobeProgramInterface { return newKprobePrograms(c) } +func (c *BpfmanV1alpha1Client) TcNsPrograms(namespace string) TcNsProgramInterface { + return newTcNsPrograms(c, namespace) +} + func (c *BpfmanV1alpha1Client) TcPrograms() TcProgramInterface { return newTcPrograms(c) } +func (c *BpfmanV1alpha1Client) TcxNsPrograms(namespace string) TcxNsProgramInterface { + return newTcxNsPrograms(c, namespace) +} + func (c *BpfmanV1alpha1Client) TcxPrograms() TcxProgramInterface { return newTcxPrograms(c) } @@ -77,10 +99,18 @@ func (c *BpfmanV1alpha1Client) TracepointPrograms() TracepointProgramInterface { return newTracepointPrograms(c) } +func (c *BpfmanV1alpha1Client) UprobeNsPrograms(namespace string) UprobeNsProgramInterface { + return newUprobeNsPrograms(c, namespace) +} + func (c *BpfmanV1alpha1Client) UprobePrograms() UprobeProgramInterface { return newUprobePrograms(c) } +func (c *BpfmanV1alpha1Client) XdpNsPrograms(namespace string) XdpNsProgramInterface { + return newXdpNsPrograms(c, namespace) +} + func (c *BpfmanV1alpha1Client) XdpPrograms() XdpProgramInterface { return newXdpPrograms(c) } diff --git a/pkg/client/clientset/typed/apis/v1alpha1/bpfnsapplication.go b/pkg/client/clientset/typed/apis/v1alpha1/bpfnsapplication.go new file mode 100644 index 000000000..ed96b8cad --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/bpfnsapplication.go @@ -0,0 +1,195 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + scheme "github.com/bpfman/bpfman-operator/pkg/client/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// BpfNsApplicationsGetter has a method to return a BpfNsApplicationInterface. +// A group's client should implement this interface. +type BpfNsApplicationsGetter interface { + BpfNsApplications(namespace string) BpfNsApplicationInterface +} + +// BpfNsApplicationInterface has methods to work with BpfNsApplication resources. +type BpfNsApplicationInterface interface { + Create(ctx context.Context, bpfNsApplication *v1alpha1.BpfNsApplication, opts v1.CreateOptions) (*v1alpha1.BpfNsApplication, error) + Update(ctx context.Context, bpfNsApplication *v1alpha1.BpfNsApplication, opts v1.UpdateOptions) (*v1alpha1.BpfNsApplication, error) + UpdateStatus(ctx context.Context, bpfNsApplication *v1alpha1.BpfNsApplication, opts v1.UpdateOptions) (*v1alpha1.BpfNsApplication, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.BpfNsApplication, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.BpfNsApplicationList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.BpfNsApplication, err error) + BpfNsApplicationExpansion +} + +// bpfNsApplications implements BpfNsApplicationInterface +type bpfNsApplications struct { + client rest.Interface + ns string +} + +// newBpfNsApplications returns a BpfNsApplications +func newBpfNsApplications(c *BpfmanV1alpha1Client, namespace string) *bpfNsApplications { + return &bpfNsApplications{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the bpfNsApplication, and returns the corresponding bpfNsApplication object, and an error if there is any. +func (c *bpfNsApplications) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.BpfNsApplication, err error) { + result = &v1alpha1.BpfNsApplication{} + err = c.client.Get(). + Namespace(c.ns). + Resource("bpfnsapplications"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of BpfNsApplications that match those selectors. +func (c *bpfNsApplications) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.BpfNsApplicationList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.BpfNsApplicationList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("bpfnsapplications"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested bpfNsApplications. +func (c *bpfNsApplications) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("bpfnsapplications"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a bpfNsApplication and creates it. Returns the server's representation of the bpfNsApplication, and an error, if there is any. +func (c *bpfNsApplications) Create(ctx context.Context, bpfNsApplication *v1alpha1.BpfNsApplication, opts v1.CreateOptions) (result *v1alpha1.BpfNsApplication, err error) { + result = &v1alpha1.BpfNsApplication{} + err = c.client.Post(). + Namespace(c.ns). + Resource("bpfnsapplications"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(bpfNsApplication). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a bpfNsApplication and updates it. Returns the server's representation of the bpfNsApplication, and an error, if there is any. +func (c *bpfNsApplications) Update(ctx context.Context, bpfNsApplication *v1alpha1.BpfNsApplication, opts v1.UpdateOptions) (result *v1alpha1.BpfNsApplication, err error) { + result = &v1alpha1.BpfNsApplication{} + err = c.client.Put(). + Namespace(c.ns). + Resource("bpfnsapplications"). + Name(bpfNsApplication.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(bpfNsApplication). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *bpfNsApplications) UpdateStatus(ctx context.Context, bpfNsApplication *v1alpha1.BpfNsApplication, opts v1.UpdateOptions) (result *v1alpha1.BpfNsApplication, err error) { + result = &v1alpha1.BpfNsApplication{} + err = c.client.Put(). + Namespace(c.ns). + Resource("bpfnsapplications"). + Name(bpfNsApplication.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(bpfNsApplication). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the bpfNsApplication and deletes it. Returns an error if one occurs. +func (c *bpfNsApplications) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("bpfnsapplications"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *bpfNsApplications) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("bpfnsapplications"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched bpfNsApplication. +func (c *bpfNsApplications) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.BpfNsApplication, err error) { + result = &v1alpha1.BpfNsApplication{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("bpfnsapplications"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/bpfnsprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/bpfnsprogram.go new file mode 100644 index 000000000..b71f972a0 --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/bpfnsprogram.go @@ -0,0 +1,195 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + scheme "github.com/bpfman/bpfman-operator/pkg/client/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// BpfNsProgramsGetter has a method to return a BpfNsProgramInterface. +// A group's client should implement this interface. +type BpfNsProgramsGetter interface { + BpfNsPrograms(namespace string) BpfNsProgramInterface +} + +// BpfNsProgramInterface has methods to work with BpfNsProgram resources. +type BpfNsProgramInterface interface { + Create(ctx context.Context, bpfNsProgram *v1alpha1.BpfNsProgram, opts v1.CreateOptions) (*v1alpha1.BpfNsProgram, error) + Update(ctx context.Context, bpfNsProgram *v1alpha1.BpfNsProgram, opts v1.UpdateOptions) (*v1alpha1.BpfNsProgram, error) + UpdateStatus(ctx context.Context, bpfNsProgram *v1alpha1.BpfNsProgram, opts v1.UpdateOptions) (*v1alpha1.BpfNsProgram, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.BpfNsProgram, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.BpfNsProgramList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.BpfNsProgram, err error) + BpfNsProgramExpansion +} + +// bpfNsPrograms implements BpfNsProgramInterface +type bpfNsPrograms struct { + client rest.Interface + ns string +} + +// newBpfNsPrograms returns a BpfNsPrograms +func newBpfNsPrograms(c *BpfmanV1alpha1Client, namespace string) *bpfNsPrograms { + return &bpfNsPrograms{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the bpfNsProgram, and returns the corresponding bpfNsProgram object, and an error if there is any. +func (c *bpfNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.BpfNsProgram, err error) { + result = &v1alpha1.BpfNsProgram{} + err = c.client.Get(). + Namespace(c.ns). + Resource("bpfnsprograms"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of BpfNsPrograms that match those selectors. +func (c *bpfNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.BpfNsProgramList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.BpfNsProgramList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("bpfnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested bpfNsPrograms. +func (c *bpfNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("bpfnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a bpfNsProgram and creates it. Returns the server's representation of the bpfNsProgram, and an error, if there is any. +func (c *bpfNsPrograms) Create(ctx context.Context, bpfNsProgram *v1alpha1.BpfNsProgram, opts v1.CreateOptions) (result *v1alpha1.BpfNsProgram, err error) { + result = &v1alpha1.BpfNsProgram{} + err = c.client.Post(). + Namespace(c.ns). + Resource("bpfnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(bpfNsProgram). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a bpfNsProgram and updates it. Returns the server's representation of the bpfNsProgram, and an error, if there is any. +func (c *bpfNsPrograms) Update(ctx context.Context, bpfNsProgram *v1alpha1.BpfNsProgram, opts v1.UpdateOptions) (result *v1alpha1.BpfNsProgram, err error) { + result = &v1alpha1.BpfNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("bpfnsprograms"). + Name(bpfNsProgram.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(bpfNsProgram). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *bpfNsPrograms) UpdateStatus(ctx context.Context, bpfNsProgram *v1alpha1.BpfNsProgram, opts v1.UpdateOptions) (result *v1alpha1.BpfNsProgram, err error) { + result = &v1alpha1.BpfNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("bpfnsprograms"). + Name(bpfNsProgram.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(bpfNsProgram). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the bpfNsProgram and deletes it. Returns an error if one occurs. +func (c *bpfNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("bpfnsprograms"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *bpfNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("bpfnsprograms"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched bpfNsProgram. +func (c *bpfNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.BpfNsProgram, err error) { + result = &v1alpha1.BpfNsProgram{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("bpfnsprograms"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_apis_client.go b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_apis_client.go index b83fbca87..9e7d2f76c 100644 --- a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_apis_client.go +++ b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_apis_client.go @@ -32,6 +32,14 @@ func (c *FakeBpfmanV1alpha1) BpfApplications() v1alpha1.BpfApplicationInterface return &FakeBpfApplications{c} } +func (c *FakeBpfmanV1alpha1) BpfNsApplications(namespace string) v1alpha1.BpfNsApplicationInterface { + return &FakeBpfNsApplications{c, namespace} +} + +func (c *FakeBpfmanV1alpha1) BpfNsPrograms(namespace string) v1alpha1.BpfNsProgramInterface { + return &FakeBpfNsPrograms{c, namespace} +} + func (c *FakeBpfmanV1alpha1) BpfPrograms() v1alpha1.BpfProgramInterface { return &FakeBpfPrograms{c} } @@ -48,10 +56,18 @@ func (c *FakeBpfmanV1alpha1) KprobePrograms() v1alpha1.KprobeProgramInterface { return &FakeKprobePrograms{c} } +func (c *FakeBpfmanV1alpha1) TcNsPrograms(namespace string) v1alpha1.TcNsProgramInterface { + return &FakeTcNsPrograms{c, namespace} +} + func (c *FakeBpfmanV1alpha1) TcPrograms() v1alpha1.TcProgramInterface { return &FakeTcPrograms{c} } +func (c *FakeBpfmanV1alpha1) TcxNsPrograms(namespace string) v1alpha1.TcxNsProgramInterface { + return &FakeTcxNsPrograms{c, namespace} +} + func (c *FakeBpfmanV1alpha1) TcxPrograms() v1alpha1.TcxProgramInterface { return &FakeTcxPrograms{c} } @@ -60,10 +76,18 @@ func (c *FakeBpfmanV1alpha1) TracepointPrograms() v1alpha1.TracepointProgramInte return &FakeTracepointPrograms{c} } +func (c *FakeBpfmanV1alpha1) UprobeNsPrograms(namespace string) v1alpha1.UprobeNsProgramInterface { + return &FakeUprobeNsPrograms{c, namespace} +} + func (c *FakeBpfmanV1alpha1) UprobePrograms() v1alpha1.UprobeProgramInterface { return &FakeUprobePrograms{c} } +func (c *FakeBpfmanV1alpha1) XdpNsPrograms(namespace string) v1alpha1.XdpNsProgramInterface { + return &FakeXdpNsPrograms{c, namespace} +} + func (c *FakeBpfmanV1alpha1) XdpPrograms() v1alpha1.XdpProgramInterface { return &FakeXdpPrograms{c} } diff --git a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_bpfnsapplication.go b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_bpfnsapplication.go new file mode 100644 index 000000000..614874b91 --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_bpfnsapplication.go @@ -0,0 +1,141 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeBpfNsApplications implements BpfNsApplicationInterface +type FakeBpfNsApplications struct { + Fake *FakeBpfmanV1alpha1 + ns string +} + +var bpfnsapplicationsResource = v1alpha1.SchemeGroupVersion.WithResource("bpfnsapplications") + +var bpfnsapplicationsKind = v1alpha1.SchemeGroupVersion.WithKind("BpfNsApplication") + +// Get takes name of the bpfNsApplication, and returns the corresponding bpfNsApplication object, and an error if there is any. +func (c *FakeBpfNsApplications) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.BpfNsApplication, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(bpfnsapplicationsResource, c.ns, name), &v1alpha1.BpfNsApplication{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsApplication), err +} + +// List takes label and field selectors, and returns the list of BpfNsApplications that match those selectors. +func (c *FakeBpfNsApplications) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.BpfNsApplicationList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(bpfnsapplicationsResource, bpfnsapplicationsKind, c.ns, opts), &v1alpha1.BpfNsApplicationList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.BpfNsApplicationList{ListMeta: obj.(*v1alpha1.BpfNsApplicationList).ListMeta} + for _, item := range obj.(*v1alpha1.BpfNsApplicationList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested bpfNsApplications. +func (c *FakeBpfNsApplications) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(bpfnsapplicationsResource, c.ns, opts)) + +} + +// Create takes the representation of a bpfNsApplication and creates it. Returns the server's representation of the bpfNsApplication, and an error, if there is any. +func (c *FakeBpfNsApplications) Create(ctx context.Context, bpfNsApplication *v1alpha1.BpfNsApplication, opts v1.CreateOptions) (result *v1alpha1.BpfNsApplication, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(bpfnsapplicationsResource, c.ns, bpfNsApplication), &v1alpha1.BpfNsApplication{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsApplication), err +} + +// Update takes the representation of a bpfNsApplication and updates it. Returns the server's representation of the bpfNsApplication, and an error, if there is any. +func (c *FakeBpfNsApplications) Update(ctx context.Context, bpfNsApplication *v1alpha1.BpfNsApplication, opts v1.UpdateOptions) (result *v1alpha1.BpfNsApplication, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(bpfnsapplicationsResource, c.ns, bpfNsApplication), &v1alpha1.BpfNsApplication{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsApplication), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeBpfNsApplications) UpdateStatus(ctx context.Context, bpfNsApplication *v1alpha1.BpfNsApplication, opts v1.UpdateOptions) (*v1alpha1.BpfNsApplication, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(bpfnsapplicationsResource, "status", c.ns, bpfNsApplication), &v1alpha1.BpfNsApplication{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsApplication), err +} + +// Delete takes name of the bpfNsApplication and deletes it. Returns an error if one occurs. +func (c *FakeBpfNsApplications) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(bpfnsapplicationsResource, c.ns, name, opts), &v1alpha1.BpfNsApplication{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeBpfNsApplications) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(bpfnsapplicationsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.BpfNsApplicationList{}) + return err +} + +// Patch applies the patch and returns the patched bpfNsApplication. +func (c *FakeBpfNsApplications) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.BpfNsApplication, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(bpfnsapplicationsResource, c.ns, name, pt, data, subresources...), &v1alpha1.BpfNsApplication{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsApplication), err +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_bpfnsprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_bpfnsprogram.go new file mode 100644 index 000000000..2c029a7a0 --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_bpfnsprogram.go @@ -0,0 +1,141 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeBpfNsPrograms implements BpfNsProgramInterface +type FakeBpfNsPrograms struct { + Fake *FakeBpfmanV1alpha1 + ns string +} + +var bpfnsprogramsResource = v1alpha1.SchemeGroupVersion.WithResource("bpfnsprograms") + +var bpfnsprogramsKind = v1alpha1.SchemeGroupVersion.WithKind("BpfNsProgram") + +// Get takes name of the bpfNsProgram, and returns the corresponding bpfNsProgram object, and an error if there is any. +func (c *FakeBpfNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.BpfNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(bpfnsprogramsResource, c.ns, name), &v1alpha1.BpfNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsProgram), err +} + +// List takes label and field selectors, and returns the list of BpfNsPrograms that match those selectors. +func (c *FakeBpfNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.BpfNsProgramList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(bpfnsprogramsResource, bpfnsprogramsKind, c.ns, opts), &v1alpha1.BpfNsProgramList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.BpfNsProgramList{ListMeta: obj.(*v1alpha1.BpfNsProgramList).ListMeta} + for _, item := range obj.(*v1alpha1.BpfNsProgramList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested bpfNsPrograms. +func (c *FakeBpfNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(bpfnsprogramsResource, c.ns, opts)) + +} + +// Create takes the representation of a bpfNsProgram and creates it. Returns the server's representation of the bpfNsProgram, and an error, if there is any. +func (c *FakeBpfNsPrograms) Create(ctx context.Context, bpfNsProgram *v1alpha1.BpfNsProgram, opts v1.CreateOptions) (result *v1alpha1.BpfNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(bpfnsprogramsResource, c.ns, bpfNsProgram), &v1alpha1.BpfNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsProgram), err +} + +// Update takes the representation of a bpfNsProgram and updates it. Returns the server's representation of the bpfNsProgram, and an error, if there is any. +func (c *FakeBpfNsPrograms) Update(ctx context.Context, bpfNsProgram *v1alpha1.BpfNsProgram, opts v1.UpdateOptions) (result *v1alpha1.BpfNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(bpfnsprogramsResource, c.ns, bpfNsProgram), &v1alpha1.BpfNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsProgram), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeBpfNsPrograms) UpdateStatus(ctx context.Context, bpfNsProgram *v1alpha1.BpfNsProgram, opts v1.UpdateOptions) (*v1alpha1.BpfNsProgram, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(bpfnsprogramsResource, "status", c.ns, bpfNsProgram), &v1alpha1.BpfNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsProgram), err +} + +// Delete takes name of the bpfNsProgram and deletes it. Returns an error if one occurs. +func (c *FakeBpfNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(bpfnsprogramsResource, c.ns, name, opts), &v1alpha1.BpfNsProgram{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeBpfNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(bpfnsprogramsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.BpfNsProgramList{}) + return err +} + +// Patch applies the patch and returns the patched bpfNsProgram. +func (c *FakeBpfNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.BpfNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(bpfnsprogramsResource, c.ns, name, pt, data, subresources...), &v1alpha1.BpfNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.BpfNsProgram), err +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_tcnsprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_tcnsprogram.go new file mode 100644 index 000000000..afc7b00ec --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_tcnsprogram.go @@ -0,0 +1,141 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeTcNsPrograms implements TcNsProgramInterface +type FakeTcNsPrograms struct { + Fake *FakeBpfmanV1alpha1 + ns string +} + +var tcnsprogramsResource = v1alpha1.SchemeGroupVersion.WithResource("tcnsprograms") + +var tcnsprogramsKind = v1alpha1.SchemeGroupVersion.WithKind("TcNsProgram") + +// Get takes name of the tcNsProgram, and returns the corresponding tcNsProgram object, and an error if there is any. +func (c *FakeTcNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.TcNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(tcnsprogramsResource, c.ns, name), &v1alpha1.TcNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcNsProgram), err +} + +// List takes label and field selectors, and returns the list of TcNsPrograms that match those selectors. +func (c *FakeTcNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.TcNsProgramList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(tcnsprogramsResource, tcnsprogramsKind, c.ns, opts), &v1alpha1.TcNsProgramList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.TcNsProgramList{ListMeta: obj.(*v1alpha1.TcNsProgramList).ListMeta} + for _, item := range obj.(*v1alpha1.TcNsProgramList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested tcNsPrograms. +func (c *FakeTcNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(tcnsprogramsResource, c.ns, opts)) + +} + +// Create takes the representation of a tcNsProgram and creates it. Returns the server's representation of the tcNsProgram, and an error, if there is any. +func (c *FakeTcNsPrograms) Create(ctx context.Context, tcNsProgram *v1alpha1.TcNsProgram, opts v1.CreateOptions) (result *v1alpha1.TcNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(tcnsprogramsResource, c.ns, tcNsProgram), &v1alpha1.TcNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcNsProgram), err +} + +// Update takes the representation of a tcNsProgram and updates it. Returns the server's representation of the tcNsProgram, and an error, if there is any. +func (c *FakeTcNsPrograms) Update(ctx context.Context, tcNsProgram *v1alpha1.TcNsProgram, opts v1.UpdateOptions) (result *v1alpha1.TcNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(tcnsprogramsResource, c.ns, tcNsProgram), &v1alpha1.TcNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcNsProgram), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeTcNsPrograms) UpdateStatus(ctx context.Context, tcNsProgram *v1alpha1.TcNsProgram, opts v1.UpdateOptions) (*v1alpha1.TcNsProgram, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(tcnsprogramsResource, "status", c.ns, tcNsProgram), &v1alpha1.TcNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcNsProgram), err +} + +// Delete takes name of the tcNsProgram and deletes it. Returns an error if one occurs. +func (c *FakeTcNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(tcnsprogramsResource, c.ns, name, opts), &v1alpha1.TcNsProgram{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeTcNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(tcnsprogramsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.TcNsProgramList{}) + return err +} + +// Patch applies the patch and returns the patched tcNsProgram. +func (c *FakeTcNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TcNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(tcnsprogramsResource, c.ns, name, pt, data, subresources...), &v1alpha1.TcNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcNsProgram), err +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_tcxnsprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_tcxnsprogram.go new file mode 100644 index 000000000..faeb6da68 --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_tcxnsprogram.go @@ -0,0 +1,141 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeTcxNsPrograms implements TcxNsProgramInterface +type FakeTcxNsPrograms struct { + Fake *FakeBpfmanV1alpha1 + ns string +} + +var tcxnsprogramsResource = v1alpha1.SchemeGroupVersion.WithResource("tcxnsprograms") + +var tcxnsprogramsKind = v1alpha1.SchemeGroupVersion.WithKind("TcxNsProgram") + +// Get takes name of the tcxNsProgram, and returns the corresponding tcxNsProgram object, and an error if there is any. +func (c *FakeTcxNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.TcxNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(tcxnsprogramsResource, c.ns, name), &v1alpha1.TcxNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcxNsProgram), err +} + +// List takes label and field selectors, and returns the list of TcxNsPrograms that match those selectors. +func (c *FakeTcxNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.TcxNsProgramList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(tcxnsprogramsResource, tcxnsprogramsKind, c.ns, opts), &v1alpha1.TcxNsProgramList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.TcxNsProgramList{ListMeta: obj.(*v1alpha1.TcxNsProgramList).ListMeta} + for _, item := range obj.(*v1alpha1.TcxNsProgramList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested tcxNsPrograms. +func (c *FakeTcxNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(tcxnsprogramsResource, c.ns, opts)) + +} + +// Create takes the representation of a tcxNsProgram and creates it. Returns the server's representation of the tcxNsProgram, and an error, if there is any. +func (c *FakeTcxNsPrograms) Create(ctx context.Context, tcxNsProgram *v1alpha1.TcxNsProgram, opts v1.CreateOptions) (result *v1alpha1.TcxNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(tcxnsprogramsResource, c.ns, tcxNsProgram), &v1alpha1.TcxNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcxNsProgram), err +} + +// Update takes the representation of a tcxNsProgram and updates it. Returns the server's representation of the tcxNsProgram, and an error, if there is any. +func (c *FakeTcxNsPrograms) Update(ctx context.Context, tcxNsProgram *v1alpha1.TcxNsProgram, opts v1.UpdateOptions) (result *v1alpha1.TcxNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(tcxnsprogramsResource, c.ns, tcxNsProgram), &v1alpha1.TcxNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcxNsProgram), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeTcxNsPrograms) UpdateStatus(ctx context.Context, tcxNsProgram *v1alpha1.TcxNsProgram, opts v1.UpdateOptions) (*v1alpha1.TcxNsProgram, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(tcxnsprogramsResource, "status", c.ns, tcxNsProgram), &v1alpha1.TcxNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcxNsProgram), err +} + +// Delete takes name of the tcxNsProgram and deletes it. Returns an error if one occurs. +func (c *FakeTcxNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(tcxnsprogramsResource, c.ns, name, opts), &v1alpha1.TcxNsProgram{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeTcxNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(tcxnsprogramsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.TcxNsProgramList{}) + return err +} + +// Patch applies the patch and returns the patched tcxNsProgram. +func (c *FakeTcxNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TcxNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(tcxnsprogramsResource, c.ns, name, pt, data, subresources...), &v1alpha1.TcxNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TcxNsProgram), err +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_uprobensprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_uprobensprogram.go new file mode 100644 index 000000000..9166c453d --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_uprobensprogram.go @@ -0,0 +1,141 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeUprobeNsPrograms implements UprobeNsProgramInterface +type FakeUprobeNsPrograms struct { + Fake *FakeBpfmanV1alpha1 + ns string +} + +var uprobensprogramsResource = v1alpha1.SchemeGroupVersion.WithResource("uprobensprograms") + +var uprobensprogramsKind = v1alpha1.SchemeGroupVersion.WithKind("UprobeNsProgram") + +// Get takes name of the uprobeNsProgram, and returns the corresponding uprobeNsProgram object, and an error if there is any. +func (c *FakeUprobeNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.UprobeNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(uprobensprogramsResource, c.ns, name), &v1alpha1.UprobeNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.UprobeNsProgram), err +} + +// List takes label and field selectors, and returns the list of UprobeNsPrograms that match those selectors. +func (c *FakeUprobeNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.UprobeNsProgramList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(uprobensprogramsResource, uprobensprogramsKind, c.ns, opts), &v1alpha1.UprobeNsProgramList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.UprobeNsProgramList{ListMeta: obj.(*v1alpha1.UprobeNsProgramList).ListMeta} + for _, item := range obj.(*v1alpha1.UprobeNsProgramList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested uprobeNsPrograms. +func (c *FakeUprobeNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(uprobensprogramsResource, c.ns, opts)) + +} + +// Create takes the representation of a uprobeNsProgram and creates it. Returns the server's representation of the uprobeNsProgram, and an error, if there is any. +func (c *FakeUprobeNsPrograms) Create(ctx context.Context, uprobeNsProgram *v1alpha1.UprobeNsProgram, opts v1.CreateOptions) (result *v1alpha1.UprobeNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(uprobensprogramsResource, c.ns, uprobeNsProgram), &v1alpha1.UprobeNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.UprobeNsProgram), err +} + +// Update takes the representation of a uprobeNsProgram and updates it. Returns the server's representation of the uprobeNsProgram, and an error, if there is any. +func (c *FakeUprobeNsPrograms) Update(ctx context.Context, uprobeNsProgram *v1alpha1.UprobeNsProgram, opts v1.UpdateOptions) (result *v1alpha1.UprobeNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(uprobensprogramsResource, c.ns, uprobeNsProgram), &v1alpha1.UprobeNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.UprobeNsProgram), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeUprobeNsPrograms) UpdateStatus(ctx context.Context, uprobeNsProgram *v1alpha1.UprobeNsProgram, opts v1.UpdateOptions) (*v1alpha1.UprobeNsProgram, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(uprobensprogramsResource, "status", c.ns, uprobeNsProgram), &v1alpha1.UprobeNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.UprobeNsProgram), err +} + +// Delete takes name of the uprobeNsProgram and deletes it. Returns an error if one occurs. +func (c *FakeUprobeNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(uprobensprogramsResource, c.ns, name, opts), &v1alpha1.UprobeNsProgram{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeUprobeNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(uprobensprogramsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.UprobeNsProgramList{}) + return err +} + +// Patch applies the patch and returns the patched uprobeNsProgram. +func (c *FakeUprobeNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.UprobeNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(uprobensprogramsResource, c.ns, name, pt, data, subresources...), &v1alpha1.UprobeNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.UprobeNsProgram), err +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_xdpnsprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_xdpnsprogram.go new file mode 100644 index 000000000..ef9ee7dc8 --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/fake/fake_xdpnsprogram.go @@ -0,0 +1,141 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeXdpNsPrograms implements XdpNsProgramInterface +type FakeXdpNsPrograms struct { + Fake *FakeBpfmanV1alpha1 + ns string +} + +var xdpnsprogramsResource = v1alpha1.SchemeGroupVersion.WithResource("xdpnsprograms") + +var xdpnsprogramsKind = v1alpha1.SchemeGroupVersion.WithKind("XdpNsProgram") + +// Get takes name of the xdpNsProgram, and returns the corresponding xdpNsProgram object, and an error if there is any. +func (c *FakeXdpNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.XdpNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(xdpnsprogramsResource, c.ns, name), &v1alpha1.XdpNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.XdpNsProgram), err +} + +// List takes label and field selectors, and returns the list of XdpNsPrograms that match those selectors. +func (c *FakeXdpNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.XdpNsProgramList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(xdpnsprogramsResource, xdpnsprogramsKind, c.ns, opts), &v1alpha1.XdpNsProgramList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.XdpNsProgramList{ListMeta: obj.(*v1alpha1.XdpNsProgramList).ListMeta} + for _, item := range obj.(*v1alpha1.XdpNsProgramList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested xdpNsPrograms. +func (c *FakeXdpNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(xdpnsprogramsResource, c.ns, opts)) + +} + +// Create takes the representation of a xdpNsProgram and creates it. Returns the server's representation of the xdpNsProgram, and an error, if there is any. +func (c *FakeXdpNsPrograms) Create(ctx context.Context, xdpNsProgram *v1alpha1.XdpNsProgram, opts v1.CreateOptions) (result *v1alpha1.XdpNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(xdpnsprogramsResource, c.ns, xdpNsProgram), &v1alpha1.XdpNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.XdpNsProgram), err +} + +// Update takes the representation of a xdpNsProgram and updates it. Returns the server's representation of the xdpNsProgram, and an error, if there is any. +func (c *FakeXdpNsPrograms) Update(ctx context.Context, xdpNsProgram *v1alpha1.XdpNsProgram, opts v1.UpdateOptions) (result *v1alpha1.XdpNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(xdpnsprogramsResource, c.ns, xdpNsProgram), &v1alpha1.XdpNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.XdpNsProgram), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeXdpNsPrograms) UpdateStatus(ctx context.Context, xdpNsProgram *v1alpha1.XdpNsProgram, opts v1.UpdateOptions) (*v1alpha1.XdpNsProgram, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(xdpnsprogramsResource, "status", c.ns, xdpNsProgram), &v1alpha1.XdpNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.XdpNsProgram), err +} + +// Delete takes name of the xdpNsProgram and deletes it. Returns an error if one occurs. +func (c *FakeXdpNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(xdpnsprogramsResource, c.ns, name, opts), &v1alpha1.XdpNsProgram{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeXdpNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(xdpnsprogramsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.XdpNsProgramList{}) + return err +} + +// Patch applies the patch and returns the patched xdpNsProgram. +func (c *FakeXdpNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.XdpNsProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(xdpnsprogramsResource, c.ns, name, pt, data, subresources...), &v1alpha1.XdpNsProgram{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.XdpNsProgram), err +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/generated_expansion.go b/pkg/client/clientset/typed/apis/v1alpha1/generated_expansion.go index c514790d0..3b956a1a6 100644 --- a/pkg/client/clientset/typed/apis/v1alpha1/generated_expansion.go +++ b/pkg/client/clientset/typed/apis/v1alpha1/generated_expansion.go @@ -20,6 +20,10 @@ package v1alpha1 type BpfApplicationExpansion interface{} +type BpfNsApplicationExpansion interface{} + +type BpfNsProgramExpansion interface{} + type BpfProgramExpansion interface{} type FentryProgramExpansion interface{} @@ -28,12 +32,20 @@ type FexitProgramExpansion interface{} type KprobeProgramExpansion interface{} +type TcNsProgramExpansion interface{} + type TcProgramExpansion interface{} +type TcxNsProgramExpansion interface{} + type TcxProgramExpansion interface{} type TracepointProgramExpansion interface{} +type UprobeNsProgramExpansion interface{} + type UprobeProgramExpansion interface{} +type XdpNsProgramExpansion interface{} + type XdpProgramExpansion interface{} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/tcnsprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/tcnsprogram.go new file mode 100644 index 000000000..9795fa01a --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/tcnsprogram.go @@ -0,0 +1,195 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + scheme "github.com/bpfman/bpfman-operator/pkg/client/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// TcNsProgramsGetter has a method to return a TcNsProgramInterface. +// A group's client should implement this interface. +type TcNsProgramsGetter interface { + TcNsPrograms(namespace string) TcNsProgramInterface +} + +// TcNsProgramInterface has methods to work with TcNsProgram resources. +type TcNsProgramInterface interface { + Create(ctx context.Context, tcNsProgram *v1alpha1.TcNsProgram, opts v1.CreateOptions) (*v1alpha1.TcNsProgram, error) + Update(ctx context.Context, tcNsProgram *v1alpha1.TcNsProgram, opts v1.UpdateOptions) (*v1alpha1.TcNsProgram, error) + UpdateStatus(ctx context.Context, tcNsProgram *v1alpha1.TcNsProgram, opts v1.UpdateOptions) (*v1alpha1.TcNsProgram, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.TcNsProgram, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.TcNsProgramList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TcNsProgram, err error) + TcNsProgramExpansion +} + +// tcNsPrograms implements TcNsProgramInterface +type tcNsPrograms struct { + client rest.Interface + ns string +} + +// newTcNsPrograms returns a TcNsPrograms +func newTcNsPrograms(c *BpfmanV1alpha1Client, namespace string) *tcNsPrograms { + return &tcNsPrograms{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the tcNsProgram, and returns the corresponding tcNsProgram object, and an error if there is any. +func (c *tcNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.TcNsProgram, err error) { + result = &v1alpha1.TcNsProgram{} + err = c.client.Get(). + Namespace(c.ns). + Resource("tcnsprograms"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of TcNsPrograms that match those selectors. +func (c *tcNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.TcNsProgramList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.TcNsProgramList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("tcnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested tcNsPrograms. +func (c *tcNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("tcnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a tcNsProgram and creates it. Returns the server's representation of the tcNsProgram, and an error, if there is any. +func (c *tcNsPrograms) Create(ctx context.Context, tcNsProgram *v1alpha1.TcNsProgram, opts v1.CreateOptions) (result *v1alpha1.TcNsProgram, err error) { + result = &v1alpha1.TcNsProgram{} + err = c.client.Post(). + Namespace(c.ns). + Resource("tcnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(tcNsProgram). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a tcNsProgram and updates it. Returns the server's representation of the tcNsProgram, and an error, if there is any. +func (c *tcNsPrograms) Update(ctx context.Context, tcNsProgram *v1alpha1.TcNsProgram, opts v1.UpdateOptions) (result *v1alpha1.TcNsProgram, err error) { + result = &v1alpha1.TcNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("tcnsprograms"). + Name(tcNsProgram.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(tcNsProgram). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *tcNsPrograms) UpdateStatus(ctx context.Context, tcNsProgram *v1alpha1.TcNsProgram, opts v1.UpdateOptions) (result *v1alpha1.TcNsProgram, err error) { + result = &v1alpha1.TcNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("tcnsprograms"). + Name(tcNsProgram.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(tcNsProgram). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the tcNsProgram and deletes it. Returns an error if one occurs. +func (c *tcNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("tcnsprograms"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *tcNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("tcnsprograms"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched tcNsProgram. +func (c *tcNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TcNsProgram, err error) { + result = &v1alpha1.TcNsProgram{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("tcnsprograms"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/tcxnsprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/tcxnsprogram.go new file mode 100644 index 000000000..2c2ec75c6 --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/tcxnsprogram.go @@ -0,0 +1,195 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + scheme "github.com/bpfman/bpfman-operator/pkg/client/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// TcxNsProgramsGetter has a method to return a TcxNsProgramInterface. +// A group's client should implement this interface. +type TcxNsProgramsGetter interface { + TcxNsPrograms(namespace string) TcxNsProgramInterface +} + +// TcxNsProgramInterface has methods to work with TcxNsProgram resources. +type TcxNsProgramInterface interface { + Create(ctx context.Context, tcxNsProgram *v1alpha1.TcxNsProgram, opts v1.CreateOptions) (*v1alpha1.TcxNsProgram, error) + Update(ctx context.Context, tcxNsProgram *v1alpha1.TcxNsProgram, opts v1.UpdateOptions) (*v1alpha1.TcxNsProgram, error) + UpdateStatus(ctx context.Context, tcxNsProgram *v1alpha1.TcxNsProgram, opts v1.UpdateOptions) (*v1alpha1.TcxNsProgram, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.TcxNsProgram, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.TcxNsProgramList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TcxNsProgram, err error) + TcxNsProgramExpansion +} + +// tcxNsPrograms implements TcxNsProgramInterface +type tcxNsPrograms struct { + client rest.Interface + ns string +} + +// newTcxNsPrograms returns a TcxNsPrograms +func newTcxNsPrograms(c *BpfmanV1alpha1Client, namespace string) *tcxNsPrograms { + return &tcxNsPrograms{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the tcxNsProgram, and returns the corresponding tcxNsProgram object, and an error if there is any. +func (c *tcxNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.TcxNsProgram, err error) { + result = &v1alpha1.TcxNsProgram{} + err = c.client.Get(). + Namespace(c.ns). + Resource("tcxnsprograms"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of TcxNsPrograms that match those selectors. +func (c *tcxNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.TcxNsProgramList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.TcxNsProgramList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("tcxnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested tcxNsPrograms. +func (c *tcxNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("tcxnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a tcxNsProgram and creates it. Returns the server's representation of the tcxNsProgram, and an error, if there is any. +func (c *tcxNsPrograms) Create(ctx context.Context, tcxNsProgram *v1alpha1.TcxNsProgram, opts v1.CreateOptions) (result *v1alpha1.TcxNsProgram, err error) { + result = &v1alpha1.TcxNsProgram{} + err = c.client.Post(). + Namespace(c.ns). + Resource("tcxnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(tcxNsProgram). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a tcxNsProgram and updates it. Returns the server's representation of the tcxNsProgram, and an error, if there is any. +func (c *tcxNsPrograms) Update(ctx context.Context, tcxNsProgram *v1alpha1.TcxNsProgram, opts v1.UpdateOptions) (result *v1alpha1.TcxNsProgram, err error) { + result = &v1alpha1.TcxNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("tcxnsprograms"). + Name(tcxNsProgram.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(tcxNsProgram). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *tcxNsPrograms) UpdateStatus(ctx context.Context, tcxNsProgram *v1alpha1.TcxNsProgram, opts v1.UpdateOptions) (result *v1alpha1.TcxNsProgram, err error) { + result = &v1alpha1.TcxNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("tcxnsprograms"). + Name(tcxNsProgram.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(tcxNsProgram). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the tcxNsProgram and deletes it. Returns an error if one occurs. +func (c *tcxNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("tcxnsprograms"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *tcxNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("tcxnsprograms"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched tcxNsProgram. +func (c *tcxNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TcxNsProgram, err error) { + result = &v1alpha1.TcxNsProgram{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("tcxnsprograms"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/uprobensprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/uprobensprogram.go new file mode 100644 index 000000000..824f80991 --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/uprobensprogram.go @@ -0,0 +1,195 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + scheme "github.com/bpfman/bpfman-operator/pkg/client/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// UprobeNsProgramsGetter has a method to return a UprobeNsProgramInterface. +// A group's client should implement this interface. +type UprobeNsProgramsGetter interface { + UprobeNsPrograms(namespace string) UprobeNsProgramInterface +} + +// UprobeNsProgramInterface has methods to work with UprobeNsProgram resources. +type UprobeNsProgramInterface interface { + Create(ctx context.Context, uprobeNsProgram *v1alpha1.UprobeNsProgram, opts v1.CreateOptions) (*v1alpha1.UprobeNsProgram, error) + Update(ctx context.Context, uprobeNsProgram *v1alpha1.UprobeNsProgram, opts v1.UpdateOptions) (*v1alpha1.UprobeNsProgram, error) + UpdateStatus(ctx context.Context, uprobeNsProgram *v1alpha1.UprobeNsProgram, opts v1.UpdateOptions) (*v1alpha1.UprobeNsProgram, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.UprobeNsProgram, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.UprobeNsProgramList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.UprobeNsProgram, err error) + UprobeNsProgramExpansion +} + +// uprobeNsPrograms implements UprobeNsProgramInterface +type uprobeNsPrograms struct { + client rest.Interface + ns string +} + +// newUprobeNsPrograms returns a UprobeNsPrograms +func newUprobeNsPrograms(c *BpfmanV1alpha1Client, namespace string) *uprobeNsPrograms { + return &uprobeNsPrograms{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the uprobeNsProgram, and returns the corresponding uprobeNsProgram object, and an error if there is any. +func (c *uprobeNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.UprobeNsProgram, err error) { + result = &v1alpha1.UprobeNsProgram{} + err = c.client.Get(). + Namespace(c.ns). + Resource("uprobensprograms"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of UprobeNsPrograms that match those selectors. +func (c *uprobeNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.UprobeNsProgramList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.UprobeNsProgramList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("uprobensprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested uprobeNsPrograms. +func (c *uprobeNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("uprobensprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a uprobeNsProgram and creates it. Returns the server's representation of the uprobeNsProgram, and an error, if there is any. +func (c *uprobeNsPrograms) Create(ctx context.Context, uprobeNsProgram *v1alpha1.UprobeNsProgram, opts v1.CreateOptions) (result *v1alpha1.UprobeNsProgram, err error) { + result = &v1alpha1.UprobeNsProgram{} + err = c.client.Post(). + Namespace(c.ns). + Resource("uprobensprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(uprobeNsProgram). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a uprobeNsProgram and updates it. Returns the server's representation of the uprobeNsProgram, and an error, if there is any. +func (c *uprobeNsPrograms) Update(ctx context.Context, uprobeNsProgram *v1alpha1.UprobeNsProgram, opts v1.UpdateOptions) (result *v1alpha1.UprobeNsProgram, err error) { + result = &v1alpha1.UprobeNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("uprobensprograms"). + Name(uprobeNsProgram.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(uprobeNsProgram). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *uprobeNsPrograms) UpdateStatus(ctx context.Context, uprobeNsProgram *v1alpha1.UprobeNsProgram, opts v1.UpdateOptions) (result *v1alpha1.UprobeNsProgram, err error) { + result = &v1alpha1.UprobeNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("uprobensprograms"). + Name(uprobeNsProgram.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(uprobeNsProgram). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the uprobeNsProgram and deletes it. Returns an error if one occurs. +func (c *uprobeNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("uprobensprograms"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *uprobeNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("uprobensprograms"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched uprobeNsProgram. +func (c *uprobeNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.UprobeNsProgram, err error) { + result = &v1alpha1.UprobeNsProgram{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("uprobensprograms"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/typed/apis/v1alpha1/xdpnsprogram.go b/pkg/client/clientset/typed/apis/v1alpha1/xdpnsprogram.go new file mode 100644 index 000000000..09742d71d --- /dev/null +++ b/pkg/client/clientset/typed/apis/v1alpha1/xdpnsprogram.go @@ -0,0 +1,195 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + scheme "github.com/bpfman/bpfman-operator/pkg/client/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// XdpNsProgramsGetter has a method to return a XdpNsProgramInterface. +// A group's client should implement this interface. +type XdpNsProgramsGetter interface { + XdpNsPrograms(namespace string) XdpNsProgramInterface +} + +// XdpNsProgramInterface has methods to work with XdpNsProgram resources. +type XdpNsProgramInterface interface { + Create(ctx context.Context, xdpNsProgram *v1alpha1.XdpNsProgram, opts v1.CreateOptions) (*v1alpha1.XdpNsProgram, error) + Update(ctx context.Context, xdpNsProgram *v1alpha1.XdpNsProgram, opts v1.UpdateOptions) (*v1alpha1.XdpNsProgram, error) + UpdateStatus(ctx context.Context, xdpNsProgram *v1alpha1.XdpNsProgram, opts v1.UpdateOptions) (*v1alpha1.XdpNsProgram, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.XdpNsProgram, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.XdpNsProgramList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.XdpNsProgram, err error) + XdpNsProgramExpansion +} + +// xdpNsPrograms implements XdpNsProgramInterface +type xdpNsPrograms struct { + client rest.Interface + ns string +} + +// newXdpNsPrograms returns a XdpNsPrograms +func newXdpNsPrograms(c *BpfmanV1alpha1Client, namespace string) *xdpNsPrograms { + return &xdpNsPrograms{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the xdpNsProgram, and returns the corresponding xdpNsProgram object, and an error if there is any. +func (c *xdpNsPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.XdpNsProgram, err error) { + result = &v1alpha1.XdpNsProgram{} + err = c.client.Get(). + Namespace(c.ns). + Resource("xdpnsprograms"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of XdpNsPrograms that match those selectors. +func (c *xdpNsPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.XdpNsProgramList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.XdpNsProgramList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("xdpnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested xdpNsPrograms. +func (c *xdpNsPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("xdpnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a xdpNsProgram and creates it. Returns the server's representation of the xdpNsProgram, and an error, if there is any. +func (c *xdpNsPrograms) Create(ctx context.Context, xdpNsProgram *v1alpha1.XdpNsProgram, opts v1.CreateOptions) (result *v1alpha1.XdpNsProgram, err error) { + result = &v1alpha1.XdpNsProgram{} + err = c.client.Post(). + Namespace(c.ns). + Resource("xdpnsprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(xdpNsProgram). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a xdpNsProgram and updates it. Returns the server's representation of the xdpNsProgram, and an error, if there is any. +func (c *xdpNsPrograms) Update(ctx context.Context, xdpNsProgram *v1alpha1.XdpNsProgram, opts v1.UpdateOptions) (result *v1alpha1.XdpNsProgram, err error) { + result = &v1alpha1.XdpNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("xdpnsprograms"). + Name(xdpNsProgram.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(xdpNsProgram). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *xdpNsPrograms) UpdateStatus(ctx context.Context, xdpNsProgram *v1alpha1.XdpNsProgram, opts v1.UpdateOptions) (result *v1alpha1.XdpNsProgram, err error) { + result = &v1alpha1.XdpNsProgram{} + err = c.client.Put(). + Namespace(c.ns). + Resource("xdpnsprograms"). + Name(xdpNsProgram.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(xdpNsProgram). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the xdpNsProgram and deletes it. Returns an error if one occurs. +func (c *xdpNsPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("xdpnsprograms"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *xdpNsPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("xdpnsprograms"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched xdpNsProgram. +func (c *xdpNsPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.XdpNsProgram, err error) { + result = &v1alpha1.XdpNsProgram{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("xdpnsprograms"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/externalversions/apis/v1alpha1/bpfnsapplication.go b/pkg/client/externalversions/apis/v1alpha1/bpfnsapplication.go new file mode 100644 index 000000000..d8b075589 --- /dev/null +++ b/pkg/client/externalversions/apis/v1alpha1/bpfnsapplication.go @@ -0,0 +1,90 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + apisv1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1alpha1 "github.com/bpfman/bpfman-operator/pkg/client/apis/v1alpha1" + clientset "github.com/bpfman/bpfman-operator/pkg/client/clientset" + internalinterfaces "github.com/bpfman/bpfman-operator/pkg/client/externalversions/internalinterfaces" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// BpfNsApplicationInformer provides access to a shared informer and lister for +// BpfNsApplications. +type BpfNsApplicationInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.BpfNsApplicationLister +} + +type bpfNsApplicationInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewBpfNsApplicationInformer constructs a new informer for BpfNsApplication type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewBpfNsApplicationInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredBpfNsApplicationInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredBpfNsApplicationInformer constructs a new informer for BpfNsApplication type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredBpfNsApplicationInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().BpfNsApplications(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().BpfNsApplications(namespace).Watch(context.TODO(), options) + }, + }, + &apisv1alpha1.BpfNsApplication{}, + resyncPeriod, + indexers, + ) +} + +func (f *bpfNsApplicationInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredBpfNsApplicationInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *bpfNsApplicationInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisv1alpha1.BpfNsApplication{}, f.defaultInformer) +} + +func (f *bpfNsApplicationInformer) Lister() v1alpha1.BpfNsApplicationLister { + return v1alpha1.NewBpfNsApplicationLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/externalversions/apis/v1alpha1/bpfnsprogram.go b/pkg/client/externalversions/apis/v1alpha1/bpfnsprogram.go new file mode 100644 index 000000000..d03eeb308 --- /dev/null +++ b/pkg/client/externalversions/apis/v1alpha1/bpfnsprogram.go @@ -0,0 +1,90 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + apisv1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1alpha1 "github.com/bpfman/bpfman-operator/pkg/client/apis/v1alpha1" + clientset "github.com/bpfman/bpfman-operator/pkg/client/clientset" + internalinterfaces "github.com/bpfman/bpfman-operator/pkg/client/externalversions/internalinterfaces" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// BpfNsProgramInformer provides access to a shared informer and lister for +// BpfNsPrograms. +type BpfNsProgramInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.BpfNsProgramLister +} + +type bpfNsProgramInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewBpfNsProgramInformer constructs a new informer for BpfNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewBpfNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredBpfNsProgramInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredBpfNsProgramInformer constructs a new informer for BpfNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredBpfNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().BpfNsPrograms(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().BpfNsPrograms(namespace).Watch(context.TODO(), options) + }, + }, + &apisv1alpha1.BpfNsProgram{}, + resyncPeriod, + indexers, + ) +} + +func (f *bpfNsProgramInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredBpfNsProgramInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *bpfNsProgramInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisv1alpha1.BpfNsProgram{}, f.defaultInformer) +} + +func (f *bpfNsProgramInformer) Lister() v1alpha1.BpfNsProgramLister { + return v1alpha1.NewBpfNsProgramLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/externalversions/apis/v1alpha1/interface.go b/pkg/client/externalversions/apis/v1alpha1/interface.go index 432597f9d..7ca2dcbec 100644 --- a/pkg/client/externalversions/apis/v1alpha1/interface.go +++ b/pkg/client/externalversions/apis/v1alpha1/interface.go @@ -26,6 +26,10 @@ import ( type Interface interface { // BpfApplications returns a BpfApplicationInformer. BpfApplications() BpfApplicationInformer + // BpfNsApplications returns a BpfNsApplicationInformer. + BpfNsApplications() BpfNsApplicationInformer + // BpfNsPrograms returns a BpfNsProgramInformer. + BpfNsPrograms() BpfNsProgramInformer // BpfPrograms returns a BpfProgramInformer. BpfPrograms() BpfProgramInformer // FentryPrograms returns a FentryProgramInformer. @@ -34,14 +38,22 @@ type Interface interface { FexitPrograms() FexitProgramInformer // KprobePrograms returns a KprobeProgramInformer. KprobePrograms() KprobeProgramInformer + // TcNsPrograms returns a TcNsProgramInformer. + TcNsPrograms() TcNsProgramInformer // TcPrograms returns a TcProgramInformer. TcPrograms() TcProgramInformer + // TcxNsPrograms returns a TcxNsProgramInformer. + TcxNsPrograms() TcxNsProgramInformer // TcxPrograms returns a TcxProgramInformer. TcxPrograms() TcxProgramInformer // TracepointPrograms returns a TracepointProgramInformer. TracepointPrograms() TracepointProgramInformer + // UprobeNsPrograms returns a UprobeNsProgramInformer. + UprobeNsPrograms() UprobeNsProgramInformer // UprobePrograms returns a UprobeProgramInformer. UprobePrograms() UprobeProgramInformer + // XdpNsPrograms returns a XdpNsProgramInformer. + XdpNsPrograms() XdpNsProgramInformer // XdpPrograms returns a XdpProgramInformer. XdpPrograms() XdpProgramInformer } @@ -62,6 +74,16 @@ func (v *version) BpfApplications() BpfApplicationInformer { return &bpfApplicationInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// BpfNsApplications returns a BpfNsApplicationInformer. +func (v *version) BpfNsApplications() BpfNsApplicationInformer { + return &bpfNsApplicationInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// BpfNsPrograms returns a BpfNsProgramInformer. +func (v *version) BpfNsPrograms() BpfNsProgramInformer { + return &bpfNsProgramInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // BpfPrograms returns a BpfProgramInformer. func (v *version) BpfPrograms() BpfProgramInformer { return &bpfProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} @@ -82,11 +104,21 @@ func (v *version) KprobePrograms() KprobeProgramInformer { return &kprobeProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// TcNsPrograms returns a TcNsProgramInformer. +func (v *version) TcNsPrograms() TcNsProgramInformer { + return &tcNsProgramInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // TcPrograms returns a TcProgramInformer. func (v *version) TcPrograms() TcProgramInformer { return &tcProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// TcxNsPrograms returns a TcxNsProgramInformer. +func (v *version) TcxNsPrograms() TcxNsProgramInformer { + return &tcxNsProgramInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // TcxPrograms returns a TcxProgramInformer. func (v *version) TcxPrograms() TcxProgramInformer { return &tcxProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} @@ -97,11 +129,21 @@ func (v *version) TracepointPrograms() TracepointProgramInformer { return &tracepointProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// UprobeNsPrograms returns a UprobeNsProgramInformer. +func (v *version) UprobeNsPrograms() UprobeNsProgramInformer { + return &uprobeNsProgramInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // UprobePrograms returns a UprobeProgramInformer. func (v *version) UprobePrograms() UprobeProgramInformer { return &uprobeProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// XdpNsPrograms returns a XdpNsProgramInformer. +func (v *version) XdpNsPrograms() XdpNsProgramInformer { + return &xdpNsProgramInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // XdpPrograms returns a XdpProgramInformer. func (v *version) XdpPrograms() XdpProgramInformer { return &xdpProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} diff --git a/pkg/client/externalversions/apis/v1alpha1/tcnsprogram.go b/pkg/client/externalversions/apis/v1alpha1/tcnsprogram.go new file mode 100644 index 000000000..3f99fd4e3 --- /dev/null +++ b/pkg/client/externalversions/apis/v1alpha1/tcnsprogram.go @@ -0,0 +1,90 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + apisv1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1alpha1 "github.com/bpfman/bpfman-operator/pkg/client/apis/v1alpha1" + clientset "github.com/bpfman/bpfman-operator/pkg/client/clientset" + internalinterfaces "github.com/bpfman/bpfman-operator/pkg/client/externalversions/internalinterfaces" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// TcNsProgramInformer provides access to a shared informer and lister for +// TcNsPrograms. +type TcNsProgramInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.TcNsProgramLister +} + +type tcNsProgramInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewTcNsProgramInformer constructs a new informer for TcNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewTcNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredTcNsProgramInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredTcNsProgramInformer constructs a new informer for TcNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredTcNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().TcNsPrograms(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().TcNsPrograms(namespace).Watch(context.TODO(), options) + }, + }, + &apisv1alpha1.TcNsProgram{}, + resyncPeriod, + indexers, + ) +} + +func (f *tcNsProgramInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredTcNsProgramInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *tcNsProgramInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisv1alpha1.TcNsProgram{}, f.defaultInformer) +} + +func (f *tcNsProgramInformer) Lister() v1alpha1.TcNsProgramLister { + return v1alpha1.NewTcNsProgramLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/externalversions/apis/v1alpha1/tcxnsprogram.go b/pkg/client/externalversions/apis/v1alpha1/tcxnsprogram.go new file mode 100644 index 000000000..d77878c0d --- /dev/null +++ b/pkg/client/externalversions/apis/v1alpha1/tcxnsprogram.go @@ -0,0 +1,90 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + apisv1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1alpha1 "github.com/bpfman/bpfman-operator/pkg/client/apis/v1alpha1" + clientset "github.com/bpfman/bpfman-operator/pkg/client/clientset" + internalinterfaces "github.com/bpfman/bpfman-operator/pkg/client/externalversions/internalinterfaces" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// TcxNsProgramInformer provides access to a shared informer and lister for +// TcxNsPrograms. +type TcxNsProgramInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.TcxNsProgramLister +} + +type tcxNsProgramInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewTcxNsProgramInformer constructs a new informer for TcxNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewTcxNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredTcxNsProgramInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredTcxNsProgramInformer constructs a new informer for TcxNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredTcxNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().TcxNsPrograms(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().TcxNsPrograms(namespace).Watch(context.TODO(), options) + }, + }, + &apisv1alpha1.TcxNsProgram{}, + resyncPeriod, + indexers, + ) +} + +func (f *tcxNsProgramInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredTcxNsProgramInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *tcxNsProgramInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisv1alpha1.TcxNsProgram{}, f.defaultInformer) +} + +func (f *tcxNsProgramInformer) Lister() v1alpha1.TcxNsProgramLister { + return v1alpha1.NewTcxNsProgramLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/externalversions/apis/v1alpha1/uprobensprogram.go b/pkg/client/externalversions/apis/v1alpha1/uprobensprogram.go new file mode 100644 index 000000000..d842b9ece --- /dev/null +++ b/pkg/client/externalversions/apis/v1alpha1/uprobensprogram.go @@ -0,0 +1,90 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + apisv1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1alpha1 "github.com/bpfman/bpfman-operator/pkg/client/apis/v1alpha1" + clientset "github.com/bpfman/bpfman-operator/pkg/client/clientset" + internalinterfaces "github.com/bpfman/bpfman-operator/pkg/client/externalversions/internalinterfaces" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// UprobeNsProgramInformer provides access to a shared informer and lister for +// UprobeNsPrograms. +type UprobeNsProgramInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.UprobeNsProgramLister +} + +type uprobeNsProgramInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewUprobeNsProgramInformer constructs a new informer for UprobeNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewUprobeNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredUprobeNsProgramInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredUprobeNsProgramInformer constructs a new informer for UprobeNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredUprobeNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().UprobeNsPrograms(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().UprobeNsPrograms(namespace).Watch(context.TODO(), options) + }, + }, + &apisv1alpha1.UprobeNsProgram{}, + resyncPeriod, + indexers, + ) +} + +func (f *uprobeNsProgramInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredUprobeNsProgramInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *uprobeNsProgramInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisv1alpha1.UprobeNsProgram{}, f.defaultInformer) +} + +func (f *uprobeNsProgramInformer) Lister() v1alpha1.UprobeNsProgramLister { + return v1alpha1.NewUprobeNsProgramLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/externalversions/apis/v1alpha1/xdpnsprogram.go b/pkg/client/externalversions/apis/v1alpha1/xdpnsprogram.go new file mode 100644 index 000000000..b43c10380 --- /dev/null +++ b/pkg/client/externalversions/apis/v1alpha1/xdpnsprogram.go @@ -0,0 +1,90 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + apisv1alpha1 "github.com/bpfman/bpfman-operator/apis/v1alpha1" + v1alpha1 "github.com/bpfman/bpfman-operator/pkg/client/apis/v1alpha1" + clientset "github.com/bpfman/bpfman-operator/pkg/client/clientset" + internalinterfaces "github.com/bpfman/bpfman-operator/pkg/client/externalversions/internalinterfaces" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// XdpNsProgramInformer provides access to a shared informer and lister for +// XdpNsPrograms. +type XdpNsProgramInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.XdpNsProgramLister +} + +type xdpNsProgramInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewXdpNsProgramInformer constructs a new informer for XdpNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewXdpNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredXdpNsProgramInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredXdpNsProgramInformer constructs a new informer for XdpNsProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredXdpNsProgramInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().XdpNsPrograms(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().XdpNsPrograms(namespace).Watch(context.TODO(), options) + }, + }, + &apisv1alpha1.XdpNsProgram{}, + resyncPeriod, + indexers, + ) +} + +func (f *xdpNsProgramInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredXdpNsProgramInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *xdpNsProgramInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisv1alpha1.XdpNsProgram{}, f.defaultInformer) +} + +func (f *xdpNsProgramInformer) Lister() v1alpha1.XdpNsProgramLister { + return v1alpha1.NewXdpNsProgramLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/externalversions/generic.go b/pkg/client/externalversions/generic.go index 4db1e4a1e..2d084ef57 100644 --- a/pkg/client/externalversions/generic.go +++ b/pkg/client/externalversions/generic.go @@ -55,6 +55,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource // Group=bpfman.io, Version=v1alpha1 case v1alpha1.SchemeGroupVersion.WithResource("bpfapplications"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().BpfApplications().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("bpfnsapplications"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().BpfNsApplications().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("bpfnsprograms"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().BpfNsPrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("bpfprograms"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().BpfPrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("fentryprograms"): @@ -63,14 +67,22 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().FexitPrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("kprobeprograms"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().KprobePrograms().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("tcnsprograms"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().TcNsPrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("tcprograms"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().TcPrograms().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("tcxnsprograms"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().TcxNsPrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("tcxprograms"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().TcxPrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("tracepointprograms"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().TracepointPrograms().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("uprobensprograms"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().UprobeNsPrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("uprobeprograms"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().UprobePrograms().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("xdpnsprograms"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().XdpNsPrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("xdpprograms"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().XdpPrograms().Informer()}, nil