diff --git a/.fmf/version b/.fmf/version
new file mode 100644
index 000000000..d00491fd7
--- /dev/null
+++ b/.fmf/version
@@ -0,0 +1 @@
+1
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index feed9ad44..3b2e22c7d 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -3,11 +3,21 @@
version: 2
updates:
- - package-ecosystem: "cargo"
- directory: "/"
+ - package-ecosystem: cargo
+ directory: /
schedule:
- interval: "weekly"
- - package-ecosystem: "github-actions"
- directory: "/"
+ interval: weekly
+ groups:
+ production-dependencies:
+ dependency-type: production
+ development-dependencies:
+ dependency-type: development
+ - package-ecosystem: github-actions
+ directory: /
schedule:
- interval: "weekly"
+ interval: weekly
+ groups:
+ production-dependencies:
+ dependency-type: production
+ development-dependencies:
+ dependency-type: development
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 72b126086..2657b2f01 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -4,7 +4,7 @@ on: # yamllint disable-line rule:truthy
push:
branches: [main]
tags:
- - "v*"
+ - v*
pull_request:
branches:
@@ -21,7 +21,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Check License Header
- uses: apache/skywalking-eyes@a790ab8dd23a7f861c18bd6aaa9b012e3a234bce
+ uses: apache/skywalking-eyes@cd7b195c51fd3d6ad52afceb760719ddc6b3ee91
build:
runs-on: ubuntu-latest
@@ -39,7 +39,6 @@ jobs:
libssl-dev \
gcc-multilib \
libelf-dev \
- musl-tools
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
@@ -58,7 +57,9 @@ jobs:
components: rustfmt, clippy, rust-src
override: false
- - uses: Swatinem/rust-cache@v2
+ ## TODO(astoycos) Deactivate the rust-cache action until we can determine
+ ## why it's freezing at the end of the install.
+ ## - uses: Swatinem/rust-cache@v2
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
@@ -87,28 +88,37 @@ jobs:
run: |
cargo +nightly clippy --all -- --deny warnings
+ - name: Check public API
+ run: cargo xtask public-api
+
- name: Build
run: cargo build --verbose
+ - name: Build manpages
+ run: cargo xtask build-man-page
+
+ - name: Build CLI TAB Completion
+ run: cargo xtask build-completion
+
## If the push is a tag....build and upload the release bpfman binaries to an archive
- name: Build-Release
if: startsWith(github.ref, 'refs/tags/v')
run: |
- rustup target add x86_64-unknown-linux-musl
- cargo build --release --target x86_64-unknown-linux-musl
+ cargo build --release --target x86_64-unknown-linux-gnu
- name: Package-Binaries
if: startsWith(github.ref, 'refs/tags/v')
run: |
- tar -czvf bpfman-linux-x86_64.tar.gz ./target/x86_64-unknown-linux-musl/release/bpfman
+ cd target/x86_64-unknown-linux-gnu/release
+ tar -czvf bpfman-linux-x86_64.tar.gz bpfman bpfman-rpc bpfman-ns bpf-metrics-exporter bpf-log-exporter
- name: Archive bpfman Release Binaries
if: startsWith(github.ref, 'refs/tags/v')
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: bpfman-release
path: |
- ./bpfman-linux-x86_64.tar.gz
+ ./target/x86_64-unknown-linux-gnu/release/bpfman-linux-x86_64.tar.gz
- name: Run tests
run: cargo llvm-cov test --all-features -p bpfman -p bpfman-api --lcov --output-path lcov.info
@@ -116,38 +126,55 @@ jobs:
RUST_BACKTRACE: full
- name: Archive Rust code coverage results
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
- name: coverage
+ name: coverage-rust
path: lcov.info
if-no-files-found: error
## Build go modules
build-go:
runs-on: ubuntu-latest
+ # k8s codegen requires this to be set
+ env:
+ GOPATH: ${{ github.workspace }}
defaults:
run:
- working-directory: ./bpfman-operator
+ ## For us to run the controller-gen generate commands from within
+ ## github actions the package name MUST match the directory layout
+ ## (i.e `GOPATH/src/github.com/bpfman/bpfman`). Otherwise when
+ ## running `make generate` generated code is deposited at
+ ## `home/runner/work/bpfman/bpfman/bpfman-operator/PKG_NAME` instead
+ ## of in `home/runner/work/bpfman/bpfman/bpfman-operator/pkg/client/...`.
+ ## This is annoying and gross but cannot be resolved until
+ ## https://github.com/kubernetes/kubernetes/issues/86753 is properly
+ ## addressed.
+ working-directory: ${{ env.GOPATH }}/src/github.com/bpfman/bpfman/bpfman-operator
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-go@v4
with:
- go-version: "1.21"
+ path: ${{ env.GOPATH }}/src/github.com/bpfman/bpfman
+
+ - uses: actions/setup-go@v5
+ with:
+ # prettier-ignore
+ go-version: '1.21' # yamllint disable-line rule:quoted-strings
- name: Go mod check
- working-directory: ${{ github.workspace }}
run: |
go mod tidy
- git diff --exit-code go.mod go.sum
+ git diff --exit-code ../go.mod ../go.sum
- name: Lint
- uses: golangci/golangci-lint-action@v3
+ uses: golangci/golangci-lint-action@v4
with:
+ ## https://github.com/golangci/golangci-lint-action/issues/369
+ working-directory: ${{ env.GOPATH }}/src/github.com/bpfman/bpfman
version: v1.54.2
skip-cache: true
skip-pkg-cache: true
skip-build-cache: true
- args: -v --timeout 5m --enable=gofmt
+ args: -v --timeout 5m
- name: Build Examples
run: |
@@ -163,15 +190,28 @@ jobs:
run: make test
- name: Archive Go code coverage results
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
- name: coverage
- path: ./bpfman-operator/cover.out
+ name: coverage-go
+ path: ${{ env.GOPATH }}/src/github.com/bpfman/bpfman/bpfman-operator/cover.out
if-no-files-found: error
+ build-docs:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
+ with:
+ python-version: 3.x
+ - name: Mkdocs Build
+ run: |
+ pip install mkdocs-material
+ pip install -r requirements.txt
+ mkdocs build --strict
+
basic-integration-tests:
runs-on: ubuntu-latest
- needs: ["build", "build-go"]
+ needs: [build, build-go, build-docs]
env:
CARGO_TERM_COLOR: always
steps:
@@ -209,6 +249,12 @@ jobs:
- name: Build bpfman
run: cargo build --verbose
+ - name: Build manpages
+ run: cargo xtask build-man-page
+
+ - name: Build CLI TAB Completion
+ run: cargo xtask build-completion
+
- name: Run the bpfman installer
run: sudo ./scripts/setup.sh install
@@ -216,24 +262,30 @@ jobs:
run: sleep 5
- name: Verify the bpfman systemd service is active
- run: systemctl is-active bpfman
+ run: systemctl is-active bpfman.socket
- name: Verify the CLI can reach bpfman
run: sudo bpfman list
+ - name: Verify the manpages are installed
+ run: man bpfman list
+
- name: Stop the bpfman systemd service
- run: sudo systemctl stop bpfman
+ run: |
+ sudo systemctl stop bpfman
+ sudo ./scripts/setup.sh uninstall
- name: Run integration tests
run: cargo xtask integration-test
kubernetes-integration-tests:
- needs: ["build", "build-go"]
+ needs: [build, build-go, build-docs]
runs-on: ubuntu-latest
env:
- BPFMAN_IMG: "quay.io/bpfman/bpfman:int-test"
- BPFMAN_AGENT_IMG: "quay.io/bpfman/bpfman-agent:int-test"
- BPFMAN_OPERATOR_IMG: "quay.io/bpfman/bpfman-operator:int-test"
+ BPFMAN_IMG: quay.io/bpfman/bpfman:int-test
+ BPFMAN_AGENT_IMG: quay.io/bpfman/bpfman-agent:int-test
+ BPFMAN_OPERATOR_IMG: quay.io/bpfman/bpfman-operator:int-test
+ XDP_PASS_PRIVATE_IMAGE_CREDS: ${{ secrets.XDP_PASS_PRIVATE_IMAGE_CREDS }}
steps:
- name: Install dependencies
run: |
@@ -246,12 +298,13 @@ jobs:
libbpf-dev
- name: setup golang
- uses: actions/setup-go@v4
+ uses: actions/setup-go@v5
with:
- go-version: "^1.19"
+ # prettier-ignore
+ go-version: '1.21' # yamllint disable-line rule:quoted-strings
- name: cache go modules
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-build-codegen-${{ hashFiles('**/go.sum') }}
@@ -283,27 +336,31 @@ jobs:
## Upload diagnostics if integration test step failed.
- name: upload diagnostics
if: ${{ failure() }}
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: kubernetes-integration-test-diag
path: /tmp/ktf-diag*
if-no-files-found: ignore
coverage:
- needs: ["build", "build-go"]
+ needs: [build, build-go, build-docs]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - name: Download coverage artifacts
- uses: actions/download-artifact@v3
+ - name: Download golang coverage artifacts
+ uses: actions/download-artifact@v4
with:
- name: coverage
+ name: coverage-go
+
+ - name: Download rust coverage artifacts
+ uses: actions/download-artifact@v4
+ with:
+ name: coverage-rust
- name: Upload coverage to Codecov
- uses: codecov/codecov-action@v3
+ uses: codecov/codecov-action@v4
with:
- fail_ci_if_error: true
files: ./cover.out,./lcov.info
verbose: true
@@ -312,7 +369,7 @@ jobs:
# Publish's bpfman and bpfman-api crates to crates.io
release:
if: startsWith(github.ref, 'refs/tags/v')
- needs: ["build"]
+ needs: [build]
environment: crates.io
runs-on: ubuntu-latest
steps:
@@ -322,33 +379,42 @@ jobs:
run: |
echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
with:
name: bpfman-release
- name: release
- uses: softprops/action-gh-release@v1
+ uses: softprops/action-gh-release@v2
with:
body_path: ./changelogs/CHANGELOG-${{ env.RELEASE_VERSION }}.md
files: |
bpfman-linux-x86_64.tar.gz
- ## TODO once we're using an aya mainline version
- # - name: publish bpfman crate
- # run: cargo publish -p bpfman --token ${{ secrets.BPFMAN_DEV_TOKEN }}
+ - name: publish bpfman-csi crate
+ run: cargo publish -p bpfman-csi --token ${{ secrets.BPFMAN_DEV_TOKEN }}
+
+ - name: publish bpfman crate
+ run: cargo publish -p bpfman --token ${{ secrets.BPFMAN_DEV_TOKEN }}
- name: publish bpfman-api crate
run: cargo publish -p bpfman-api --token ${{ secrets.BPFMAN_DEV_TOKEN }}
+ - name: publish bpf-log-exporter crate
+ run: cargo publish -p bpfman-log-exporter --token ${{ secrets.BPFMAN_DEV_TOKEN }}
+
+ - name: publish bpf-metrics-exporter crate
+ run: cargo publish -p bpfman-metrics-exporter --token ${{ secrets.BPFMAN_DEV_TOKEN }}
+
build-workflow-complete:
needs:
[
- "check-license",
- "build",
- "build-go",
- "coverage",
- "basic-integration-tests",
- "kubernetes-integration-tests",
+ check-license,
+ build,
+ build-go,
+ build-docs,
+ coverage,
+ basic-integration-tests,
+ kubernetes-integration-tests,
]
runs-on: ubuntu-latest
steps:
diff --git a/.github/workflows/docs-build.yaml b/.github/workflows/docs-build.yaml
new file mode 100644
index 000000000..cd0ed448a
--- /dev/null
+++ b/.github/workflows/docs-build.yaml
@@ -0,0 +1,47 @@
+name: bpfman-docs
+
+on: # yamllint disable-line rule:truthy
+ push:
+ branches: [main]
+ tags:
+ - v*
+
+jobs:
+ build-docs:
+ runs-on: ubuntu-latest
+ timeout-minutes: 3
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: gh-pages
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
+ with:
+ python-version: 3.8
+ - uses: actions/setup-go@v5
+ with:
+ # prettier-ignore
+ go-version: '1.21' # yamllint disable-line rule:quoted-strings
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r requirements.txt
+
+ - name: Build API docs
+ run: |
+ make -C bpfman-operator apidocs.html
+
+ - name: Configure Git user
+ run: |
+ git config --local user.email "github-actions[bot]@users.noreply.github.com"
+ git config --local user.name "github-actions[bot]"
+
+ - name: Deploy Docs (Release Version)
+ if: startsWith(github.ref_name, 'v')
+ run: |
+ mike deploy --push --update-aliases ${{ github.ref_name }} latest
+
+ - name: Deploy Docs (Development Version)
+ if: github.ref_name == 'main'
+ run: |
+ mike deploy --push main
diff --git a/.github/workflows/image-build.yaml b/.github/workflows/image-build.yaml
index 5b39bbdab..257620ca5 100644
--- a/.github/workflows/image-build.yaml
+++ b/.github/workflows/image-build.yaml
@@ -4,10 +4,10 @@ on: # yamllint disable-line rule:truthy
push:
branches: [main]
tags:
- - "v*"
+ - v*
pull_request:
- paths: [".github/workflows/image-build.yaml"]
+ paths: [.github/workflows/image-build.yaml]
jobs:
build-and-push-images:
@@ -22,6 +22,7 @@ jobs:
matrix:
image:
- registry: quay.io
+ build_language: rust
repository: bpfman
image: bpfman
dockerfile: ./Containerfile.bpfman
@@ -35,6 +36,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- registry: quay.io
+ build_language: go
repository: bpfman
image: bpfman-agent
dockerfile: ./bpfman-operator/Containerfile.bpfman-agent
@@ -48,6 +50,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- registry: quay.io
+ build_language: go
repository: bpfman
image: bpfman-operator
dockerfile: ./bpfman-operator/Containerfile.bpfman-operator
@@ -61,6 +64,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- registry: quay.io
+ build_language: go
repository: bpfman
image: bpfman-operator-bundle
context: ./bpfman-operator
@@ -74,6 +78,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- registry: quay.io
+ build_language: go
repository: bpfman-userspace
image: go-xdp-counter
context: .
@@ -87,6 +92,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- registry: quay.io
+ build_language: go
repository: bpfman-userspace
image: go-tc-counter
context: .
@@ -100,6 +106,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- registry: quay.io
+ build_language: go
repository: bpfman-userspace
image: go-tracepoint-counter
context: .
@@ -113,6 +120,49 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- registry: quay.io
+ build_language: go
+ repository: bpfman-userspace
+ image: go-kprobe-counter
+ context: .
+ dockerfile: ./examples/go-kprobe-counter/container-deployment/Containerfile.go-kprobe-counter
+ tags: |
+ type=ref,event=branch
+ type=ref,event=tag
+ type=ref,event=pr
+ type=sha,format=long
+ # set latest tag for default branch
+ type=raw,value=latest,enable={{is_default_branch}}
+
+ - registry: quay.io
+ build_language: go
+ repository: bpfman-userspace
+ image: go-uprobe-counter
+ context: .
+ dockerfile: ./examples/go-uprobe-counter/container-deployment/Containerfile.go-uprobe-counter
+ tags: |
+ type=ref,event=branch
+ type=ref,event=tag
+ type=ref,event=pr
+ type=sha,format=long
+ # set latest tag for default branch
+ type=raw,value=latest,enable={{is_default_branch}}
+
+ - registry: quay.io
+ build_language: go
+ repository: bpfman-userspace
+ image: go-target
+ context: .
+ dockerfile: ./examples/go-target/container-deployment/Containerfile.go-target
+ tags: |
+ type=ref,event=branch
+ type=ref,event=tag
+ type=ref,event=pr
+ type=sha,format=long
+ # set latest tag for default branch
+ type=raw,value=latest,enable={{is_default_branch}}
+
+ - registry: quay.io
+ build_language: go
bpf_build_wrapper: go
repository: bpfman-bytecode
image: go-xdp-counter
@@ -132,6 +182,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- registry: quay.io
+ build_language: go
bpf_build_wrapper: go
repository: bpfman-bytecode
image: go-tc-counter
@@ -151,6 +202,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- registry: quay.io
+ build_language: go
bpf_build_wrapper: go
repository: bpfman-bytecode
image: go-tracepoint-counter
@@ -170,6 +222,47 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- registry: quay.io
+ build_language: go
+ bpf_build_wrapper: go
+ repository: bpfman-bytecode
+ image: go-kprobe-counter
+ context: ./examples/go-kprobe-counter
+ dockerfile: ./Containerfile.bytecode
+ build_args: |
+ PROGRAM_NAME=kprobe_counter
+ BPF_FUNCTION_NAME=kprobe_counter
+ PROGRAM_TYPE=kprobe
+ BYTECODE_FILENAME=bpf_bpfel.o
+ tags: |
+ type=ref,event=branch
+ type=ref,event=tag
+ type=ref,event=pr
+ type=sha,format=long
+ # set latest tag for default branch
+ type=raw,value=latest,enable={{is_default_branch}}
+
+ - registry: quay.io
+ build_language: go
+ bpf_build_wrapper: go
+ repository: bpfman-bytecode
+ image: go-uprobe-counter
+ context: ./examples/go-uprobe-counter
+ dockerfile: ./Containerfile.bytecode
+ build_args: |
+ PROGRAM_NAME=uprobe_counter
+ BPF_FUNCTION_NAME=uprobe_counter
+ PROGRAM_TYPE=uprobe
+ BYTECODE_FILENAME=bpf_bpfel.o
+ tags: |
+ type=ref,event=branch
+ type=ref,event=tag
+ type=ref,event=pr
+ type=sha,format=long
+ # set latest tag for default branch
+ type=raw,value=latest,enable={{is_default_branch}}
+
+ - registry: quay.io
+ build_language: rust
bpf_build_wrapper: rust
repository: bpfman-bytecode
image: xdp_pass
@@ -189,6 +282,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- registry: quay.io
+ build_language: rust
bpf_build_wrapper: rust
repository: bpfman-bytecode
image: xdp_pass_private
@@ -208,6 +302,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- registry: quay.io
+ build_language: rust
bpf_build_wrapper: rust
repository: bpfman-bytecode
image: tc_pass
@@ -227,6 +322,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- registry: quay.io
+ build_language: rust
bpf_build_wrapper: rust
repository: bpfman-bytecode
image: tracepoint
@@ -246,6 +342,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- registry: quay.io
+ build_language: rust
bpf_build_wrapper: rust
repository: bpfman-bytecode
image: uprobe
@@ -265,6 +362,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- registry: quay.io
+ build_language: rust
bpf_build_wrapper: rust
repository: bpfman-bytecode
image: uretprobe
@@ -284,6 +382,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- registry: quay.io
+ build_language: rust
bpf_build_wrapper: rust
repository: bpfman-bytecode
image: kprobe
@@ -303,6 +402,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- registry: quay.io
+ build_language: rust
bpf_build_wrapper: rust
repository: bpfman-bytecode
image: kretprobe
@@ -322,6 +422,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- registry: quay.io
+ build_language: rust
bpf_build_wrapper: rust
repository: bpfman
image: xdp-dispatcher
@@ -332,6 +433,7 @@ jobs:
type=raw,value=v1,enable=true
- registry: quay.io
+ build_language: rust
bpf_build_wrapper: rust
repository: bpfman
image: xdp-dispatcher
@@ -342,6 +444,7 @@ jobs:
type=raw,value=v2,enable=true
- registry: quay.io
+ build_language: rust
bpf_build_wrapper: rust
repository: bpfman
image: tc-dispatcher
@@ -355,12 +458,13 @@ jobs:
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-go@v4
- if: ${{ matrix.image.bpf_build_wrapper == 'go' }}
+ - uses: actions/setup-go@v5
+ if: ${{ matrix.image.build_language == 'go' }}
with:
- go-version: "1.21"
+ # prettier-ignore
+ go-version: '1.21' # yamllint disable-line rule:quoted-strings
- - uses: sigstore/cosign-installer@v3.2.0
+ - uses: sigstore/cosign-installer@v3.4.0
- uses: actions/checkout@v4
if: ${{ matrix.image.bpf_build_wrapper == 'rust' }}
@@ -369,7 +473,7 @@ jobs:
path: libbpf
- uses: actions-rs/toolchain@v1
- if: ${{ matrix.image.bpf_build_wrapper == 'rust' }}
+ if: ${{ matrix.image.build_language == 'rust' }}
with:
toolchain: stable
override: true
@@ -426,7 +530,7 @@ jobs:
- name: Extract metadata (tags, labels) for image
id: meta
- uses: docker/metadata-action@v5.0.0
+ uses: docker/metadata-action@v5.5.1
with:
images: ${{ matrix.image.registry }}/${{ matrix.image.repository }}/${{ matrix.image.image }}
tags: ${{ matrix.image.tags }}
diff --git a/.github/workflows/put-issue-in-project.yaml b/.github/workflows/put-issue-in-project.yaml
deleted file mode 100644
index c2003f943..000000000
--- a/.github/workflows/put-issue-in-project.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-name: Add all issues to bpfman project
-
-on: # yamllint disable-line rule:truthy
- issues:
- types:
- - opened
-
-jobs:
- add-to-project:
- name: Add issue to project
- runs-on: ubuntu-latest
- steps:
- - uses: actions/add-to-project@v0.5.0
- with:
- # You can target a project in a different organization
- # to the issue
- project-url: https://github.com/orgs/bpfman/projects/4
- github-token: ${{ secrets.BPFMAN_PROJECT_PAT }}
diff --git a/.gitignore b/.gitignore
index 1ab154082..a242a51ef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,13 @@ tests/_tmp
# Ignore compiled C files
.output
+
+# Ignore docs
+site/
+
+# RPM build artifacts
+*.tar.gz
+*.rpm
+results_*/
+*x86_64/
+cargo-vendor-filterer
diff --git a/.golangci.yaml b/.golangci.yaml
index d211011b5..141c21632 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -4,6 +4,15 @@ run:
# - vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
# Default: true
skip-dirs-use-default: false
+
+linters:
enable:
- gofmt
+ - errcheck
+ - gosimple
+ - govet
+ - ineffassign
+ - staticcheck
+ - unused
- typecheck
+ - loggercheck
diff --git a/.packit.sh b/.packit.sh
new file mode 100755
index 000000000..bdb7a5d60
--- /dev/null
+++ b/.packit.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+GIT_SHA=$(git rev-parse HEAD)
+GIT_SHORT_SHA=$(git rev-parse --short HEAD)
+
+sed -i "s/GITSHA/${GIT_SHA}/g" bpfman.spec
+sed -i "s/GITSHORTSHA/${GIT_SHORT_SHA}/g" bpfman.spec
+
+sed -i -r "s/Release:(\s*)\S+/Release:\1${PACKIT_RPMSPEC_RELEASE}%{?dist}/" bpfman.spec
diff --git a/.packit.yml b/.packit.yml
new file mode 100644
index 000000000..0f10a5a57
--- /dev/null
+++ b/.packit.yml
@@ -0,0 +1,49 @@
+downstream_package_name: bpfman
+upstream_project_url: https://github.com/bpfman/bpfman
+specfile_path: bpfman.spec
+prerelease_suffix_pattern: ([.\-_~^]?)(dev|alpha|beta|rc|pre(view)?)([.\-_]?\d+)?
+srpm_build_deps:
+ - cargo
+ - rust
+ - git
+ - jq
+ - openssl-devel
+actions:
+ fix-spec-file:
+ - bash .packit.sh
+ get-current-version:
+ - bash -c 'cargo metadata --format-version 1 | jq -r ".packages[] | select(.name == \"bpfman\") | .version"'
+ post-upstream-clone:
+ bash -c 'if [[ ! -d /var/tmp/cargo-vendor-filterer ]]; then git clone https://github.com/coreos/cargo-vendor-filterer.git /var/tmp/cargo-vendor-filterer; fi &&
+ cd /var/tmp/cargo-vendor-filterer &&
+ cargo build &&
+ cd - &&
+ cp /var/tmp/cargo-vendor-filterer/target/debug/cargo-vendor-filterer . &&
+ ./cargo-vendor-filterer --format tar.gz --prefix vendor bpfman-bpfman-vendor.tar.gz'
+jobs:
+ - job: copr_build
+ trigger: commit
+ branch: main
+ specfile_path: bpfman.spec
+ owner: "@ebpf-sig"
+ project: bpfman-next
+ targets:
+ - fedora-all
+ - job: tests
+ trigger: commit
+ targets:
+ - fedora-all
+ specfile_path: bpfman.spec
+ - job: copr_build
+ trigger: release
+ branch: main
+ specfile_path: bpfman.spec
+ owner: "@ebpf-sig"
+ project: bpfman
+ targets:
+ - fedora-all
+ - job: tests
+ trigger: release
+ targets:
+ - fedora-all
+ specfile_path: bpfman.spec
diff --git a/.yamllint.yaml b/.yamllint.yaml
index 7808dc35d..3bfb0a8ed 100644
--- a/.yamllint.yaml
+++ b/.yamllint.yaml
@@ -5,6 +5,9 @@ rules:
document-start: disable
comments:
min-spaces-from-content: 1
+ quoted-strings:
+ required: only-when-needed
+ quote-type: double
ignore:
- libbpf/*
- vendor/*
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c067f4fc1..a4d3c52d6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,13 +1,12 @@
# Contributing Guide
-* [New Contributor Guide](#contributing-guide)
- * [Ways to Contribute](#ways-to-contribute)
- * [Find an Issue](#find-an-issue)
- * [Ask for Help](#ask-for-help)
- * [Pull Request Lifecycle](#pull-request-lifecycle)
- * [Development Environment Setup](#development-environment-setup)
- * [Signoff Your Commits](#signoff-your-commits)
- * [Pull Request Checklist](#pull-request-checklist)
+* [Ways to Contribute](#ways-to-contribute)
+* [Find an Issue](#find-an-issue)
+* [Ask for Help](#ask-for-help)
+* [Pull Request Lifecycle](#pull-request-lifecycle)
+* [Development Environment Setup](#development-environment-setup)
+* [Signoff Your Commits](#signoff-your-commits)
+* [Pull Request Checklist](#pull-request-checklist)
Welcome! We are glad that you want to contribute to our project! 💖
@@ -81,13 +80,13 @@ Our process is currently as follows:
1. When you open a PR a maintainer will automatically be assigned for review
1. Make sure that your PR is passing CI - if you need help with failing checks please feel free to ask!
1. Once it is passing all CI checks, a maintainer will review your PR and you may be asked to make changes.
-1. When you have received at least one approval from a maintainer, your PR will be merged automiatcally.
+1. When you have received at least one approval from a maintainer, your PR will be merged automatically.
In some cases, other changes may conflict with your PR. If this happens, you will get notified by a comment in the issue that your PR requires a rebase, and the `needs-rebase` label will be applied. Once a rebase has been performed, this label will be automatically removed.
## Development Environment Setup
-[Instructions](https://bpfman.netlify.app/building-bpfman/#development-environment-setup)
+See [Setup and Building bpfman](https://bpfman.io/main/getting-started/building-bpfman/#development-environment-setup)
## Signoff Your Commits
@@ -138,8 +137,8 @@ A good commit message should describe what changed and why.
Examples:
- * bpfman: validate program section names
- * bpf: add dispatcher program test slot
+ * bpfman: validate program section names
+ * bpf: add dispatcher program test slot
2. Keep the second line blank.
3. Wrap all other lines at 72 columns (except for long URLs).
@@ -180,45 +179,46 @@ accept and merge it. We recommend that you check the following things locally
before you submit your code:
* Verify that Rust code has been formatted and that all clippy lints have been fixed:
-
-```console
-cd src/bpfman/
-cargo +nightly fmt --all -- --check
-cargo +nightly clippy --all -- --deny warnings
-```
-
* Verify that Go code has been formatted and linted
* Verify that Yaml files have been formatted (see
- [Install Yaml Formatter](https://bpfman.io/getting-started/building-bpfman/#install-yaml-formatter))
-* Verify that unit tests are passing locally (see
- [Unit Testing](https://bpfman.io/developer-guide/testing/#unit-testing)):
+ [Install Yaml Formatter](https://bpfman.io/main/getting-started/building-bpfman/#install-yaml-formatter))
+* Verify that Bash scripts have been linted using `shellcheck`
-```console
-cd src/bpfman/
-cargo test
-```
+ ```console
+ cd bpfman/
+ cargo xtask lint
+ ```
+
+* Verify that unit tests are passing locally (see
+ [Unit Testing](https://bpfman.io/main/developer-guide/testing/#unit-testing)):
+
+ ```console
+ cd bpfman/
+ cargo xtask unit-test
+ ```
+
+* Verify any changes to the bpfman API have been "blessed".
+ After running the below command, any changes to any of the files in
+ `bpfman/xtask/public-api/*.txt` indicate changes to the bpfman API.
+ Verify that these changes were intentional.
+ CI uses the latest nightly Rust toolchain, so make sure the public-apis
+ are verified against latest.
+
+ ```console
+ cd bpfman/
+ rustup update nightly
+ cargo +nightly xtask public-api --bless
+ ```
* Verify that integration tests are passing locally (see
- [Basic Integration Tests](https://bpfman.io/developer-guide/testing/#basic-integration-tests)):
+ [Basic Integration Tests](https://bpfman.io/main/developer-guide/testing/#basic-integration-tests)):
-```console
-cd src/bpfman/
-cargo xtask integration-test
-```
+ ```console
+ cd bpfman/
+ cargo xtask integration-test
+ ```
-* If developing the bpfman-operator, verify that bpfman-operator unit tests
+* If developing the bpfman-operator, verify that bpfman-operator unit and integration tests
are passing locally:
-
-```console
-cd src/bpfman/bpfman-operator/
-make test
-```
-
-* If developing the bpfman-operator, verify that bpfman-operator integration tests
- are passing locally (see
- [Kubernetes Integration Tests](https://bpfman.io/developer-guide/testing/#kubernetes-integration-tests)):
-
-```console
-cd src/bpfman/bpfman-operator/
-make test-integration
-```
+
+ See [Kubernetes Operator Tests](https://bpfman.io/main/developer-guide/testing/#kubernetes-operator-tests).
diff --git a/Cargo.lock b/Cargo.lock
index 4497f9424..c358c009b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,16 @@
# It is not intended for manual editing.
version = 3
+[[package]]
+name = "Inflector"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
+dependencies = [
+ "lazy_static",
+ "regex",
+]
+
[[package]]
name = "addr2line"
version = "0.21.0"
@@ -29,9 +39,9 @@ dependencies = [
[[package]]
name = "aes"
-version = "0.8.3"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
@@ -40,9 +50,20 @@ dependencies = [
[[package]]
name = "ahash"
-version = "0.8.6"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
+name = "ahash"
+version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"once_cell",
@@ -52,9 +73,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
-version = "1.1.2"
+version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
@@ -82,9 +103,9 @@ dependencies = [
[[package]]
name = "anstream"
-version = "0.6.4"
+version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
+checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -96,49 +117,49 @@ dependencies = [
[[package]]
name = "anstyle"
-version = "1.0.4"
+version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
+checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anstyle-parse"
-version = "0.2.2"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
-version = "1.0.0"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
-version = "3.0.1"
+version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [
"anstyle",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
]
[[package]]
name = "anyhow"
-version = "1.0.75"
+version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
+checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "assert_cmd"
-version = "2.0.12"
+version = "2.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6"
+checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8"
dependencies = [
"anstyle",
"bstr",
@@ -155,6 +176,17 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
+[[package]]
+name = "async-recursion"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.58",
+]
+
[[package]]
name = "async-stream"
version = "0.3.5"
@@ -174,25 +206,25 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.58",
]
[[package]]
name = "async-trait"
-version = "0.1.74"
+version = "0.1.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
+checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.58",
]
[[package]]
name = "autocfg"
-version = "1.1.0"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
[[package]]
name = "axum"
@@ -241,12 +273,13 @@ dependencies = [
[[package]]
name = "aya"
-version = "0.11.0"
-source = "git+https://github.com/aya-rs/aya?branch=main#33b2e45ad313f7468a7d11667f13d9c365e2a263"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90eea657cc8028447cbda5068f4e10c4fadba0131624f4f7dd1a9c46ffc8d81f"
dependencies = [
"assert_matches",
"aya-obj",
- "bitflags 2.4.1",
+ "bitflags 2.5.0",
"bytes",
"lazy_static",
"libc",
@@ -258,11 +291,12 @@ dependencies = [
[[package]]
name = "aya-obj"
version = "0.1.0"
-source = "git+https://github.com/aya-rs/aya?branch=main#33b2e45ad313f7468a7d11667f13d9c365e2a263"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c02024a307161cf3d1f052161958fd13b1a33e3e038083e58082c0700fdab85"
dependencies = [
"bytes",
"core-error",
- "hashbrown 0.14.2",
+ "hashbrown 0.14.3",
"log",
"object",
"thiserror",
@@ -270,9 +304,9 @@ dependencies = [
[[package]]
name = "backtrace"
-version = "0.3.69"
+version = "0.3.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
dependencies = [
"addr2line",
"cc",
@@ -297,9 +331,15 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
-version = "0.21.5"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64ct"
@@ -315,9 +355,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
-version = "2.4.1"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]]
name = "block-buffer"
@@ -337,12 +377,29 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "bpf-log-exporter"
+version = "0.4.0"
+dependencies = [
+ "anyhow",
+ "env_logger",
+ "futures",
+ "hex",
+ "log",
+ "netlink-packet-audit",
+ "netlink-packet-core",
+ "netlink-sys",
+ "regex",
+]
+
[[package]]
name = "bpf-metrics-exporter"
-version = "0.1.0"
+version = "0.4.0"
dependencies = [
"anyhow",
"aya",
+ "bpfman",
+ "bpfman-api",
"chrono",
"clap",
"opentelemetry",
@@ -355,15 +412,14 @@ dependencies = [
[[package]]
name = "bpfman"
-version = "0.3.1"
+version = "0.4.0"
dependencies = [
"anyhow",
"assert_matches",
"async-trait",
"aya",
"base16ct",
- "base64 0.21.5",
- "bpfman-api",
+ "base64 0.22.1",
"bpfman-csi",
"caps",
"chrono",
@@ -373,15 +429,18 @@ dependencies = [
"flate2",
"futures",
"hex",
+ "lazy_static",
"log",
"netlink-packet-route",
- "nix 0.27.1",
+ "nix 0.28.0",
"oci-distribution",
+ "rand",
"rtnetlink",
"serde",
"serde_json",
"sha2",
"sigstore",
+ "sled",
"systemd-journal-logger",
"tar",
"tempfile",
@@ -389,25 +448,52 @@ dependencies = [
"tokio",
"tokio-stream",
"toml",
- "tonic 0.10.2",
+ "tonic",
"tower",
"url",
- "users",
]
[[package]]
name = "bpfman-api"
-version = "0.3.1"
+version = "0.4.0"
dependencies = [
+ "anyhow",
+ "async-trait",
"aya",
+ "base16ct",
+ "base64 0.22.1",
+ "bpfman",
+ "bpfman-csi",
+ "caps",
+ "chrono",
"clap",
+ "env_logger",
+ "flate2",
+ "futures",
+ "hex",
+ "lazy_static",
+ "libsystemd",
"log",
- "prost 0.12.3",
+ "netlink-packet-route",
+ "nix 0.28.0",
+ "oci-distribution",
+ "prost",
+ "rand",
+ "rtnetlink",
"serde",
+ "serde_json",
+ "sha2",
+ "sigstore",
+ "sled",
+ "systemd-journal-logger",
+ "tar",
+ "tempfile",
"thiserror",
"tokio",
+ "tokio-stream",
"toml",
- "tonic 0.10.2",
+ "tonic",
+ "tower",
"url",
]
@@ -415,16 +501,29 @@ dependencies = [
name = "bpfman-csi"
version = "1.8.0"
dependencies = [
- "prost 0.12.3",
+ "prost",
"prost-types",
- "tonic 0.10.2",
+ "tonic",
+]
+
+[[package]]
+name = "bpfman-ns"
+version = "0.4.0"
+dependencies = [
+ "anyhow",
+ "aya",
+ "caps",
+ "clap",
+ "env_logger",
+ "log",
+ "nix 0.28.0",
]
[[package]]
name = "bstr"
-version = "1.8.0"
+version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
+checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
dependencies = [
"memchr",
"regex-automata",
@@ -433,9 +532,9 @@ dependencies = [
[[package]]
name = "bumpalo"
-version = "3.14.0"
+version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "byteorder"
@@ -445,21 +544,22 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
-version = "1.5.0"
+version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
[[package]]
name = "cached"
-version = "0.44.0"
+version = "0.49.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b195e4fbc4b6862bbd065b991a34750399c119797efff72492f28a5864de8700"
+checksum = "8e8e463fceca5674287f32d252fb1d94083758b8709c160efae66d263e5f4eba"
dependencies = [
+ "ahash 0.8.11",
"async-trait",
"cached_proc_macro",
"cached_proc_macro_types",
"futures",
- "hashbrown 0.13.2",
+ "hashbrown 0.14.3",
"instant",
"once_cell",
"thiserror",
@@ -468,12 +568,11 @@ dependencies = [
[[package]]
name = "cached_proc_macro"
-version = "0.17.0"
+version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b48814962d2fd604c50d2b9433c2a41a0ab567779ee2c02f7fba6eca1221f082"
+checksum = "ad9f16c0d84de31a2ab7fdf5f7783c14631f7075cf464eb3bb43119f61c9cb2a"
dependencies = [
- "cached_proc_macro_types",
- "darling",
+ "darling 0.14.4",
"proc-macro2",
"quote",
"syn 1.0.109",
@@ -481,9 +580,18 @@ dependencies = [
[[package]]
name = "cached_proc_macro_types"
-version = "0.1.0"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
+
+[[package]]
+name = "camino"
+version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663"
+checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c"
+dependencies = [
+ "serde",
+]
[[package]]
name = "caps"
@@ -495,6 +603,40 @@ dependencies = [
"thiserror",
]
+[[package]]
+name = "cargo-manifest"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf6ff49a028a52bf61913e19f5ba3b9bd34145cc97eb1649865576c8f06b408d"
+dependencies = [
+ "serde",
+ "thiserror",
+ "toml",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo_metadata"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037"
+dependencies = [
+ "camino",
+ "cargo-platform",
+ "semver",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
[[package]]
name = "cbc"
version = "0.1.2"
@@ -506,12 +648,9 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.0.83"
+version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
-dependencies = [
- "libc",
-]
+checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41"
[[package]]
name = "cesu8"
@@ -525,11 +664,17 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+[[package]]
+name = "cfg_aliases"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
+
[[package]]
name = "chrono"
-version = "0.4.31"
+version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [
"android-tzdata",
"iana-time-zone",
@@ -537,7 +682,7 @@ dependencies = [
"num-traits",
"serde",
"wasm-bindgen",
- "windows-targets 0.48.5",
+ "windows-targets 0.52.4",
]
[[package]]
@@ -553,9 +698,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.4.8"
+version = "4.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64"
+checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
dependencies = [
"clap_builder",
"clap_derive",
@@ -563,33 +708,52 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.4.8"
+version = "4.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc"
+checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
- "strsim",
+ "strsim 0.11.1",
+]
+
+[[package]]
+name = "clap_complete"
+version = "4.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbca90c87c2a04da41e95d1856e8bcd22f159bdbfa147314d2ce5218057b0e58"
+dependencies = [
+ "clap",
]
[[package]]
name = "clap_derive"
-version = "4.4.7"
+version = "4.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
+checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
dependencies = [
- "heck",
+ "heck 0.5.0",
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.58",
]
[[package]]
name = "clap_lex"
-version = "0.6.0"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
+
+[[package]]
+name = "clap_mangen"
+version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
+checksum = "74b70fc13e60c0e1d490dc50eb73a749be6d81f4ef03783df1d9b7b0c62bc937"
+dependencies = [
+ "clap",
+ "roff",
+]
[[package]]
name = "colorchoice"
@@ -599,9 +763,9 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "combine"
-version = "4.6.6"
+version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
dependencies = [
"bytes",
"memchr",
@@ -609,9 +773,9 @@ dependencies = [
[[package]]
name = "comfy-table"
-version = "7.1.0"
+version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c64043d6c7b7a4c58e39e7efccfdea7b93d885a795d0c054a69dbbf4dd52686"
+checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7"
dependencies = [
"crossterm",
"strum",
@@ -619,11 +783,24 @@ dependencies = [
"unicode-width",
]
+[[package]]
+name = "console"
+version = "0.15.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
+dependencies = [
+ "encode_unicode",
+ "lazy_static",
+ "libc",
+ "unicode-width",
+ "windows-sys 0.52.0",
+]
+
[[package]]
name = "const-oid"
-version = "0.9.5"
+version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "core-error"
@@ -636,9 +813,9 @@ dependencies = [
[[package]]
name = "core-foundation"
-version = "0.9.3"
+version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
@@ -646,38 +823,53 @@ dependencies = [
[[package]]
name = "core-foundation-sys"
-version = "0.8.4"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "cpufeatures"
-version = "0.2.11"
+version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
+checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
-version = "1.3.2"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
dependencies = [
"cfg-if",
]
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
+
[[package]]
name = "crossterm"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.5.0",
"crossterm_winapi",
"libc",
- "parking_lot",
+ "parking_lot 0.12.1",
"winapi",
]
@@ -692,9 +884,9 @@ dependencies = [
[[package]]
name = "crypto-bigint"
-version = "0.5.4"
+version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28f85c3514d2a6e64160359b45a3918c3b4178bcbf4ae5d03ab2d02e521c479a"
+checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
dependencies = [
"generic-array",
"rand_core",
@@ -730,9 +922,9 @@ dependencies = [
[[package]]
name = "curve25519-dalek"
-version = "4.1.1"
+version = "4.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c"
+checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -753,7 +945,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.58",
]
[[package]]
@@ -762,8 +954,18 @@ version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
dependencies = [
- "darling_core",
- "darling_macro",
+ "darling_core 0.14.4",
+ "darling_macro 0.14.4",
+]
+
+[[package]]
+name = "darling"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
+dependencies = [
+ "darling_core 0.20.8",
+ "darling_macro 0.20.8",
]
[[package]]
@@ -776,32 +978,63 @@ dependencies = [
"ident_case",
"proc-macro2",
"quote",
- "strsim",
+ "strsim 0.10.0",
"syn 1.0.109",
]
+[[package]]
+name = "darling_core"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim 0.10.0",
+ "syn 2.0.58",
+]
+
[[package]]
name = "darling_macro"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
dependencies = [
- "darling_core",
+ "darling_core 0.14.4",
"quote",
"syn 1.0.109",
]
+[[package]]
+name = "darling_macro"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
+dependencies = [
+ "darling_core 0.20.8",
+ "quote",
+ "syn 2.0.58",
+]
+
[[package]]
name = "data-encoding"
-version = "2.4.0"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
+
+[[package]]
+name = "decoded-char"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
+checksum = "5440d1dc8ea7cae44cda3c64568db29bfa2434aba51ae66a50c00488841a65a3"
[[package]]
name = "der"
-version = "0.7.8"
+version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
+checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
dependencies = [
"const-oid",
"der_derive",
@@ -818,9 +1051,36 @@ checksum = "5fe87ce4529967e0ba1dcf8450bab64d97dfd5010a6256187ffe2e43e6f0e049"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.58",
+]
+
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+ "serde",
+]
+
+[[package]]
+name = "dialoguer"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de"
+dependencies = [
+ "console",
+ "shell-words",
+ "thiserror",
]
+[[package]]
+name = "diff"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
+
[[package]]
name = "difflib"
version = "0.4.0"
@@ -847,15 +1107,15 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "dyn-clone"
-version = "1.0.16"
+version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d"
+checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "ecdsa"
-version = "0.16.8"
+version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4"
+checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
dependencies = [
"der",
"digest",
@@ -877,9 +1137,9 @@ dependencies = [
[[package]]
name = "ed25519-dalek"
-version = "2.1.0"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0"
+checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
dependencies = [
"curve25519-dalek",
"ed25519",
@@ -892,15 +1152,15 @@ dependencies = [
[[package]]
name = "either"
-version = "1.9.0"
+version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]]
name = "elliptic-curve"
-version = "0.13.7"
+version = "0.13.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9775b22bc152ad86a0cf23f0f348b884b26add12bf741e7ffc4d4ab2ab4d205"
+checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [
"base16ct",
"crypto-bigint",
@@ -917,11 +1177,17 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "encode_unicode"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+
[[package]]
name = "encoding_rs"
-version = "0.8.33"
+version = "0.8.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
+checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
dependencies = [
"cfg-if",
]
@@ -932,18 +1198,28 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a"
dependencies = [
- "heck",
+ "heck 0.4.1",
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.58",
+]
+
+[[package]]
+name = "env_filter"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
+dependencies = [
+ "log",
]
[[package]]
name = "env_logger"
-version = "0.10.1"
+version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
+checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9"
dependencies = [
+ "env_filter",
"log",
]
@@ -955,19 +1231,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
-version = "0.3.7"
+version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
]
[[package]]
name = "fastrand"
-version = "2.0.1"
+version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984"
[[package]]
name = "ff"
@@ -981,20 +1257,20 @@ dependencies = [
[[package]]
name = "fiat-crypto"
-version = "0.2.4"
+version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53a56f0780318174bad1c127063fd0c5fdfb35398e3cd79ffaab931a6c79df80"
+checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f"
[[package]]
name = "filetime"
-version = "0.2.22"
+version = "0.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0"
+checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
dependencies = [
"cfg-if",
"libc",
- "redox_syscall 0.3.5",
- "windows-sys 0.48.0",
+ "redox_syscall 0.4.1",
+ "windows-sys 0.52.0",
]
[[package]]
@@ -1005,15 +1281,15 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flagset"
-version = "0.4.4"
+version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d52a7e408202050813e6f1d9addadcaafef3dca7530c7ddfb005d4081cce6779"
+checksum = "cdeb3aa5e95cf9aabc17f060cfa0ced7b83f042390760ca53bf09df9968acaa1"
[[package]]
name = "flate2"
-version = "1.0.28"
+version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
dependencies = [
"crc32fast",
"libz-sys",
@@ -1027,19 +1303,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
-name = "form_urlencoded"
-version = "1.2.1"
+name = "foreign-types"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
+[[package]]
+name = "fs2"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
[[package]]
name = "futures"
-version = "0.3.29"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
dependencies = [
"futures-channel",
"futures-core",
@@ -1052,9 +1353,9 @@ dependencies = [
[[package]]
name = "futures-channel"
-version = "0.3.29"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
dependencies = [
"futures-core",
"futures-sink",
@@ -1062,15 +1363,15 @@ dependencies = [
[[package]]
name = "futures-core"
-version = "0.3.29"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
name = "futures-executor"
-version = "0.3.29"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
dependencies = [
"futures-core",
"futures-task",
@@ -1079,38 +1380,38 @@ dependencies = [
[[package]]
name = "futures-io"
-version = "0.3.29"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[package]]
name = "futures-macro"
-version = "0.3.29"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.58",
]
[[package]]
name = "futures-sink"
-version = "0.3.29"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
-version = "0.3.29"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
[[package]]
name = "futures-util"
-version = "0.3.29"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
"futures-channel",
"futures-core",
@@ -1124,6 +1425,15 @@ dependencies = [
"slab",
]
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
[[package]]
name = "generic-array"
version = "0.14.7"
@@ -1137,9 +1447,9 @@ dependencies = [
[[package]]
name = "getrandom"
-version = "0.2.11"
+version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
+checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
dependencies = [
"cfg-if",
"libc",
@@ -1148,9 +1458,9 @@ dependencies = [
[[package]]
name = "gimli"
-version = "0.28.0"
+version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "glob"
@@ -1160,15 +1470,15 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "globset"
-version = "0.4.13"
+version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d"
+checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
dependencies = [
"aho-corasick",
"bstr",
- "fnv",
"log",
- "regex",
+ "regex-automata",
+ "regex-syntax",
]
[[package]]
@@ -1184,9 +1494,9 @@ dependencies = [
[[package]]
name = "h2"
-version = "0.3.22"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
+checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
dependencies = [
"bytes",
"fnv",
@@ -1194,7 +1504,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http",
- "indexmap 2.1.0",
+ "indexmap 2.2.6",
"slab",
"tokio",
"tokio-util",
@@ -1202,24 +1512,27 @@ dependencies = [
]
[[package]]
-name = "hashbrown"
-version = "0.12.3"
+name = "hashbag"
+version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+checksum = "98f494b2060b2a8f5e63379e1e487258e014cee1b1725a735816c0107a2e9d93"
[[package]]
name = "hashbrown"
-version = "0.13.2"
+version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+dependencies = [
+ "ahash 0.7.8",
+]
[[package]]
name = "hashbrown"
-version = "0.14.2"
+version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
dependencies = [
- "ahash",
+ "ahash 0.8.11",
"allocator-api2",
]
@@ -1229,11 +1542,17 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
[[package]]
name = "hermit-abi"
-version = "0.3.3"
+version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hex"
@@ -1241,11 +1560,56 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+[[package]]
+name = "hickory-proto"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "091a6fbccf4860009355e3efc52ff4acf37a63489aad7435372d44ceeb6fbbcf"
+dependencies = [
+ "async-trait",
+ "cfg-if",
+ "data-encoding",
+ "enum-as-inner",
+ "futures-channel",
+ "futures-io",
+ "futures-util",
+ "idna 0.4.0",
+ "ipnet",
+ "once_cell",
+ "rand",
+ "thiserror",
+ "tinyvec",
+ "tokio",
+ "tracing",
+ "url",
+]
+
+[[package]]
+name = "hickory-resolver"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35b8f021164e6a984c9030023544c57789c51760065cd510572fedcfb04164e8"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "hickory-proto",
+ "ipconfig",
+ "lru-cache",
+ "once_cell",
+ "parking_lot 0.12.1",
+ "rand",
+ "resolv-conf",
+ "smallvec",
+ "thiserror",
+ "tokio",
+ "tracing",
+]
+
[[package]]
name = "hkdf"
-version = "0.12.3"
+version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437"
+checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
dependencies = [
"hmac",
]
@@ -1261,11 +1625,11 @@ dependencies = [
[[package]]
name = "home"
-version = "0.5.5"
+version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
]
[[package]]
@@ -1281,9 +1645,9 @@ dependencies = [
[[package]]
name = "http"
-version = "0.2.11"
+version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
+checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [
"bytes",
"fnv",
@@ -1292,18 +1656,18 @@ dependencies = [
[[package]]
name = "http-auth"
-version = "0.1.8"
+version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5430cacd7a1f9a02fbeb350dfc81a0e5ed42d81f3398cb0ba184017f85bdcfbc"
+checksum = "643c9bbf6a4ea8a656d6b4cd53d34f79e3f841ad5203c1a55fb7d761923bc255"
dependencies = [
"memchr",
]
[[package]]
name = "http-body"
-version = "0.4.5"
+version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
+checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http",
@@ -1324,9 +1688,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
-version = "0.14.27"
+version = "0.14.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
+checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
dependencies = [
"bytes",
"futures-channel",
@@ -1339,7 +1703,7 @@ dependencies = [
"httpdate",
"itoa",
"pin-project-lite",
- "socket2 0.4.10",
+ "socket2",
"tokio",
"tower-service",
"tracing",
@@ -1372,11 +1736,24 @@ dependencies = [
"tokio-io-timeout",
]
+[[package]]
+name = "hyper-tls"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
+dependencies = [
+ "bytes",
+ "hyper",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+]
+
[[package]]
name = "iana-time-zone"
-version = "0.1.58"
+version = "0.1.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@@ -1429,16 +1806,18 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
+ "serde",
]
[[package]]
name = "indexmap"
-version = "2.1.0"
+version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
+checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [
"equivalent",
- "hashbrown 0.14.2",
+ "hashbrown 0.14.3",
+ "serde",
]
[[package]]
@@ -1466,7 +1845,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"assert_cmd",
- "bpfman-api",
+ "bpfman",
"env_logger",
"integration-test-macros",
"inventory",
@@ -1481,14 +1860,14 @@ name = "integration-test-macros"
version = "0.1.0"
dependencies = [
"quote",
- "syn 2.0.39",
+ "syn 2.0.58",
]
[[package]]
name = "inventory"
-version = "0.3.13"
+version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0508c56cfe9bfd5dfeb0c22ab9a6abfda2f27bdca422132e494266351ed8d83c"
+checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767"
[[package]]
name = "ipconfig"
@@ -1496,7 +1875,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
dependencies = [
- "socket2 0.5.5",
+ "socket2",
"widestring",
"windows-sys 0.48.0",
"winreg",
@@ -1510,27 +1889,18 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "itertools"
-version = "0.10.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
-dependencies = [
- "either",
-]
-
-[[package]]
-name = "itertools"
-version = "0.11.0"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
[[package]]
name = "itoa"
-version = "1.0.9"
+version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "jni"
@@ -1556,13 +1926,44 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "js-sys"
-version = "0.3.65"
+version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "json-number"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c54d19ae7e6fc83aafa649707655a9a0ac956a0f62793bde4cfd193b0693fdf"
+dependencies = [
+ "lexical",
+ "ryu-js",
+ "serde",
+ "smallvec",
+]
+
+[[package]]
+name = "json-syntax"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbe45447363747ecc18deb478f945df8482edafbae21e51bdc73eab76883c6a5"
+dependencies = [
+ "decoded-char",
+ "hashbrown 0.12.3",
+ "indexmap 1.9.3",
+ "json-number",
+ "locspan",
+ "locspan-derive",
+ "ryu-js",
+ "serde",
+ "smallstr",
+ "smallvec",
+ "utf8-decode",
+]
+
[[package]]
name = "jwt"
version = "0.16.0"
@@ -1579,28 +1980,92 @@ dependencies = [
]
[[package]]
-name = "keccak"
-version = "0.1.4"
+name = "lazy_static"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
dependencies = [
- "cpufeatures",
+ "spin",
]
[[package]]
-name = "lazy_static"
-version = "1.4.0"
+name = "lexical"
+version = "6.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7aefb36fd43fef7003334742cbf77b243fcd36418a1d1bdd480d613a67968f6"
+dependencies = [
+ "lexical-core",
+]
+
+[[package]]
+name = "lexical-core"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46"
+dependencies = [
+ "lexical-parse-float",
+ "lexical-parse-integer",
+ "lexical-util",
+ "lexical-write-float",
+ "lexical-write-integer",
+]
+
+[[package]]
+name = "lexical-parse-float"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f"
+dependencies = [
+ "lexical-parse-integer",
+ "lexical-util",
+ "static_assertions",
+]
+
+[[package]]
+name = "lexical-parse-integer"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9"
+dependencies = [
+ "lexical-util",
+ "static_assertions",
+]
+
+[[package]]
+name = "lexical-util"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc"
+dependencies = [
+ "static_assertions",
+]
+
+[[package]]
+name = "lexical-write-float"
+version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862"
dependencies = [
- "spin 0.5.2",
+ "lexical-util",
+ "lexical-write-integer",
+ "static_assertions",
+]
+
+[[package]]
+name = "lexical-write-integer"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446"
+dependencies = [
+ "lexical-util",
+ "static_assertions",
]
[[package]]
name = "libc"
-version = "0.2.150"
+version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "libm"
@@ -1610,14 +2075,14 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]]
name = "libsystemd"
-version = "0.6.0"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88b9597a67aa1c81a6624603e6bd0bcefb9e0f94c9c54970ec53771082104b4e"
+checksum = "c592dc396b464005f78a5853555b9f240bc5378bf5221acc4e129910b2678869"
dependencies = [
"hmac",
"libc",
"log",
- "nix 0.26.4",
+ "nix 0.27.1",
"nom",
"once_cell",
"serde",
@@ -1628,9 +2093,9 @@ dependencies = [
[[package]]
name = "libz-sys"
-version = "1.1.12"
+version = "1.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b"
+checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9"
dependencies = [
"cc",
"pkg-config",
@@ -1645,9 +2110,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
-version = "0.4.11"
+version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
+checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "lock_api"
@@ -1659,11 +2124,29 @@ dependencies = [
"scopeguard",
]
+[[package]]
+name = "locspan"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33890449fcfac88e94352092944bf321f55e5deb4e289a6f51c87c55731200a0"
+
+[[package]]
+name = "locspan-derive"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88991223b049a3d29ca1f60c05639581336a0f3ee4bf8a659dddecc11c4961a"
+dependencies = [
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
[[package]]
name = "log"
-version = "0.4.20"
+version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
dependencies = [
"value-bag",
]
@@ -1698,36 +2181,17 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
-[[package]]
-name = "md-5"
-version = "0.10.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
-dependencies = [
- "cfg-if",
- "digest",
-]
-
[[package]]
name = "memchr"
-version = "2.6.4"
+version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
-
-[[package]]
-name = "memoffset"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
-dependencies = [
- "autocfg",
-]
+checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "memoffset"
-version = "0.9.0"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [
"autocfg",
]
@@ -1746,18 +2210,18 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
-version = "0.7.1"
+version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
dependencies = [
"adler",
]
[[package]]
name = "mio"
-version = "0.8.9"
+version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
+checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"wasi",
@@ -1766,9 +2230,27 @@ dependencies = [
[[package]]
name = "multimap"
-version = "0.8.3"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
+
+[[package]]
+name = "native-tls"
+version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
+checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
[[package]]
name = "ndk-context"
@@ -1776,6 +2258,21 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
+[[package]]
+name = "netlink-packet-audit"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af2754802fc8722062c38128d0c5dffd86fe4462877db60e736ab58b0daf5144"
+dependencies = [
+ "anyhow",
+ "byteorder",
+ "bytes",
+ "log",
+ "netlink-packet-core",
+ "netlink-packet-utils",
+ "netlink-proto",
+]
+
[[package]]
name = "netlink-packet-core"
version = "0.7.0"
@@ -1789,14 +2286,14 @@ dependencies = [
[[package]]
name = "netlink-packet-route"
-version = "0.17.1"
+version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66"
+checksum = "74c171cd77b4ee8c7708da746ce392440cb7bcf618d122ec9ecc607b12938bf4"
dependencies = [
"anyhow",
- "bitflags 1.3.2",
"byteorder",
"libc",
+ "log",
"netlink-packet-core",
"netlink-packet-utils",
]
@@ -1815,9 +2312,9 @@ dependencies = [
[[package]]
name = "netlink-proto"
-version = "0.11.2"
+version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "842c6770fc4bb33dd902f41829c61ef872b8e38de1405aa0b938b27b8fba12c3"
+checksum = "86b33524dc0968bfad349684447bfce6db937a9ac3332a1fe60c0c5a5ce63f21"
dependencies = [
"bytes",
"futures",
@@ -1830,9 +2327,9 @@ dependencies = [
[[package]]
name = "netlink-sys"
-version = "0.8.5"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411"
+checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307"
dependencies = [
"bytes",
"futures",
@@ -1843,26 +2340,27 @@ dependencies = [
[[package]]
name = "nix"
-version = "0.26.4"
+version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
+checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags 2.5.0",
"cfg-if",
"libc",
- "memoffset 0.7.1",
+ "memoffset",
]
[[package]]
name = "nix"
-version = "0.27.1"
+version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
+checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.5.0",
"cfg-if",
+ "cfg_aliases",
"libc",
- "memoffset 0.9.0",
+ "memoffset",
]
[[package]]
@@ -1888,26 +2386,30 @@ dependencies = [
"num-iter",
"num-traits",
"rand",
- "serde",
"smallvec",
"zeroize",
]
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
[[package]]
name = "num-integer"
-version = "0.1.45"
+version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
- "autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
-version = "0.1.43"
+version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
+checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9"
dependencies = [
"autocfg",
"num-integer",
@@ -1916,9 +2418,9 @@ dependencies = [
[[package]]
name = "num-traits"
-version = "0.2.17"
+version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
dependencies = [
"autocfg",
"libm",
@@ -1945,20 +2447,21 @@ dependencies = [
[[package]]
name = "object"
-version = "0.32.1"
+version = "0.32.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
dependencies = [
"memchr",
]
[[package]]
name = "oci-distribution"
-version = "0.9.4"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ac5b780ce1bd6c3c2ff72a3013f4b2d56d53ae03b20d424e99d2f6556125138"
+checksum = "2a635cabf7a6eb4e5f13e9e82bd9503b7c2461bf277132e38638a935ebd684b4"
dependencies = [
- "futures",
+ "bytes",
+ "chrono",
"futures-util",
"http",
"http-auth",
@@ -1972,20 +2475,10 @@ dependencies = [
"sha2",
"thiserror",
"tokio",
- "tokio-util",
"tracing",
"unicase",
]
-[[package]]
-name = "oid"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c19903c598813dba001b53beeae59bb77ad4892c5c1b9b3500ce4293a0d06c2"
-dependencies = [
- "serde",
-]
-
[[package]]
name = "olpc-cjson"
version = "0.1.3"
@@ -1999,25 +2492,68 @@ dependencies = [
[[package]]
name = "once_cell"
-version = "1.18.0"
+version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "opaque-debug"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+
+[[package]]
+name = "openssl"
+version = "0.10.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
+dependencies = [
+ "bitflags 2.5.0",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.58",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
[[package]]
name = "opentelemetry"
-version = "0.21.0"
+version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a"
+checksum = "900d57987be3f2aeb70d385fff9b27fb74c5723cc9a52d904d4f9c807a0667bf"
dependencies = [
"futures-core",
"futures-sink",
- "indexmap 2.1.0",
"js-sys",
"once_cell",
"pin-project-lite",
@@ -2027,9 +2563,9 @@ dependencies = [
[[package]]
name = "opentelemetry-otlp"
-version = "0.14.0"
+version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f24cda83b20ed2433c68241f918d0f6fdec8b1d43b7a9590ab4420c5095ca930"
+checksum = "1a016b8d9495c639af2145ac22387dcb88e44118e45320d9238fbf4e7889abcb"
dependencies = [
"async-trait",
"futures-core",
@@ -2038,38 +2574,35 @@ dependencies = [
"opentelemetry-proto",
"opentelemetry-semantic-conventions",
"opentelemetry_sdk",
- "prost 0.11.9",
+ "prost",
"thiserror",
"tokio",
- "tonic 0.9.2",
+ "tonic",
]
[[package]]
name = "opentelemetry-proto"
-version = "0.4.0"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2e155ce5cc812ea3d1dffbd1539aed653de4bf4882d60e6e04dcf0901d674e1"
+checksum = "3a8fddc9b68f5b80dae9d6f510b88e02396f006ad48cac349411fbecc80caae4"
dependencies = [
"opentelemetry",
"opentelemetry_sdk",
- "prost 0.11.9",
- "tonic 0.9.2",
+ "prost",
+ "tonic",
]
[[package]]
name = "opentelemetry-semantic-conventions"
-version = "0.13.0"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f5774f1ef1f982ef2a447f6ee04ec383981a3ab99c8e77a1a7b30182e65bbc84"
-dependencies = [
- "opentelemetry",
-]
+checksum = "f9ab5bd6c42fb9349dcf28af2ba9a0667f697f9bdcca045d39f2cec5543e2910"
[[package]]
name = "opentelemetry_sdk"
-version = "0.21.1"
+version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "968ba3f2ca03e90e5187f5e4f46c791ef7f2c163ae87789c8ce5f5ca3b7b7de5"
+checksum = "9e90c7113be649e31e9a0f8b5ee24ed7a16923b322c3c5ab6367469c049d6b7e"
dependencies = [
"async-trait",
"futures-channel",
@@ -2086,9 +2619,9 @@ dependencies = [
[[package]]
name = "ordered-float"
-version = "4.1.1"
+version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "536900a8093134cf9ccf00a27deb3532421099e958d9dd431135d0c7543ca1e8"
+checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e"
dependencies = [
"num-traits",
]
@@ -2117,6 +2650,17 @@ dependencies = [
"sha2",
]
+[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core 0.8.6",
+]
+
[[package]]
name = "parking_lot"
version = "0.12.1"
@@ -2124,7 +2668,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
- "parking_lot_core",
+ "parking_lot_core 0.9.9",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
+dependencies = [
+ "cfg-if",
+ "instant",
+ "libc",
+ "redox_syscall 0.2.16",
+ "smallvec",
+ "winapi",
]
[[package]]
@@ -2157,24 +2715,6 @@ version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
-[[package]]
-name = "path-absolutize"
-version = "3.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4af381fe79fa195b4909485d99f73a80792331df0625188e707854f0b3383f5"
-dependencies = [
- "path-dedot",
-]
-
-[[package]]
-name = "path-dedot"
-version = "3.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07ba0ad7e047712414213ff67533e6dd477af0a4e1d14fb52343e53d30ea9397"
-dependencies = [
- "once_cell",
-]
-
[[package]]
name = "pbkdf2"
version = "0.12.2"
@@ -2187,20 +2727,11 @@ dependencies = [
[[package]]
name = "pem"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8"
-dependencies = [
- "base64 0.13.1",
-]
-
-[[package]]
-name = "pem"
-version = "2.0.1"
+version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b13fe415cdf3c8e44518e18a7c95a13431d9bdf6d15367d82b23c377fdd441a"
+checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae"
dependencies = [
- "base64 0.21.5",
+ "base64 0.22.1",
"serde",
]
@@ -2226,100 +2757,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
dependencies = [
"fixedbitset",
- "indexmap 2.1.0",
-]
-
-[[package]]
-name = "picky"
-version = "7.0.0-rc.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52cccdaffd2f361b4b4eb70b4249bd71d89bb66cb84b7f76483ecd1640c543ce"
-dependencies = [
- "base64 0.21.5",
- "digest",
- "ed25519-dalek",
- "md-5",
- "num-bigint-dig",
- "p256",
- "p384",
- "picky-asn1",
- "picky-asn1-der",
- "picky-asn1-x509",
- "rand",
- "rand_core",
- "rsa",
- "serde",
- "sha1",
- "sha2",
- "sha3",
- "thiserror",
- "x25519-dalek",
- "zeroize",
-]
-
-[[package]]
-name = "picky-asn1"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "295eea0f33c16be21e2a98b908fdd4d73c04dd48c8480991b76dbcf0cb58b212"
-dependencies = [
- "oid",
- "serde",
- "serde_bytes",
- "zeroize",
-]
-
-[[package]]
-name = "picky-asn1-der"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5df7873a9e36d42dadb393bea5e211fe83d793c172afad5fb4ec846ec582793f"
-dependencies = [
- "picky-asn1",
- "serde",
- "serde_bytes",
-]
-
-[[package]]
-name = "picky-asn1-x509"
-version = "0.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c5f20f71a68499ff32310f418a6fad8816eac1a2859ed3f0c5c741389dd6208"
-dependencies = [
- "base64 0.21.5",
- "num-bigint-dig",
- "oid",
- "picky-asn1",
- "picky-asn1-der",
- "serde",
- "zeroize",
+ "indexmap 2.2.6",
]
[[package]]
name = "pin-project"
-version = "1.1.3"
+version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
+checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
-version = "1.1.3"
+version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
+checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.58",
]
[[package]]
name = "pin-project-lite"
-version = "0.2.13"
+version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]]
name = "pin-utils"
@@ -2367,15 +2832,15 @@ dependencies = [
[[package]]
name = "pkg-config"
-version = "0.3.27"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "platforms"
-version = "3.2.0"
+version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0"
+checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7"
[[package]]
name = "poly1305"
@@ -2388,6 +2853,12 @@ dependencies = [
"universal-hash",
]
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@@ -2396,13 +2867,12 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "predicates"
-version = "3.0.4"
+version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0"
+checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8"
dependencies = [
"anstyle",
"difflib",
- "itertools 0.11.0",
"predicates-core",
]
@@ -2424,12 +2894,12 @@ dependencies = [
[[package]]
name = "prettyplease"
-version = "0.2.15"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d"
+checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7"
dependencies = [
"proc-macro2",
- "syn 2.0.39",
+ "syn 2.0.58",
]
[[package]]
@@ -2442,89 +2912,102 @@ dependencies = [
]
[[package]]
-name = "proc-macro2"
-version = "1.0.69"
+name = "proc-macro-error"
+version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
- "unicode-ident",
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "version_check",
]
[[package]]
-name = "prost"
-version = "0.11.9"
+name = "proc-macro-error-attr"
+version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
- "bytes",
- "prost-derive 0.11.9",
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
+dependencies = [
+ "unicode-ident",
]
[[package]]
name = "prost"
-version = "0.12.3"
+version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a"
+checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29"
dependencies = [
"bytes",
- "prost-derive 0.12.3",
+ "prost-derive",
]
[[package]]
name = "prost-build"
-version = "0.12.1"
+version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bdf592881d821b83d471f8af290226c8d51402259e9bb5be7f9f8bdebbb11ac"
+checksum = "80b776a1b2dc779f5ee0641f8ade0125bc1298dd41a9a0c16d8bd57b42d222b1"
dependencies = [
"bytes",
- "heck",
- "itertools 0.11.0",
+ "heck 0.5.0",
+ "itertools",
"log",
"multimap",
"once_cell",
"petgraph",
"prettyplease",
- "prost 0.12.3",
+ "prost",
"prost-types",
"regex",
- "syn 2.0.39",
+ "syn 2.0.58",
"tempfile",
- "which",
]
[[package]]
name = "prost-derive"
-version = "0.11.9"
+version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4"
+checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
dependencies = [
"anyhow",
- "itertools 0.10.5",
+ "itertools",
"proc-macro2",
"quote",
- "syn 1.0.109",
+ "syn 2.0.58",
]
[[package]]
-name = "prost-derive"
-version = "0.12.3"
+name = "prost-types"
+version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
+checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0"
dependencies = [
- "anyhow",
- "itertools 0.11.0",
- "proc-macro2",
- "quote",
- "syn 2.0.39",
+ "prost",
]
[[package]]
-name = "prost-types"
-version = "0.12.2"
+name = "public-api"
+version = "0.33.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8339f32236f590281e2f6368276441394fcd1b2133b549cc895d0ae80f2f9a52"
+checksum = "b759eeee807ac96096fce568abeb9ef8d23db7be771a65252b1e5093fe7938c4"
dependencies = [
- "prost 0.12.3",
+ "hashbag",
+ "rustdoc-types",
+ "serde",
+ "serde_json",
+ "thiserror",
]
[[package]]
@@ -2535,9 +3018,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
-version = "1.0.33"
+version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
@@ -2580,9 +3063,9 @@ checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
[[package]]
name = "redox_syscall"
-version = "0.3.5"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags 1.3.2",
]
@@ -2598,9 +3081,9 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.10.2"
+version = "1.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
+checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
dependencies = [
"aho-corasick",
"memchr",
@@ -2610,9 +3093,9 @@ dependencies = [
[[package]]
name = "regex-automata"
-version = "0.4.3"
+version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
dependencies = [
"aho-corasick",
"memchr",
@@ -2621,30 +3104,33 @@ dependencies = [
[[package]]
name = "regex-syntax"
-version = "0.8.2"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]]
name = "reqwest"
-version = "0.11.22"
+version = "0.11.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
+checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
dependencies = [
- "base64 0.21.5",
+ "base64 0.21.7",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
+ "hickory-resolver",
"http",
"http-body",
"hyper",
"hyper-rustls",
+ "hyper-tls",
"ipnet",
"js-sys",
"log",
"mime",
+ "native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
@@ -2653,12 +3139,13 @@ dependencies = [
"serde",
"serde_json",
"serde_urlencoded",
+ "sync_wrapper",
"system-configuration",
"tokio",
+ "tokio-native-tls",
"tokio-rustls",
"tokio-util",
"tower-service",
- "trust-dns-resolver",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
@@ -2690,38 +3177,30 @@ dependencies = [
[[package]]
name = "ring"
-version = "0.16.20"
+version = "0.17.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
dependencies = [
"cc",
+ "cfg-if",
+ "getrandom",
"libc",
- "once_cell",
- "spin 0.5.2",
- "untrusted 0.7.1",
- "web-sys",
- "winapi",
+ "spin",
+ "untrusted",
+ "windows-sys 0.52.0",
]
[[package]]
-name = "ring"
-version = "0.17.5"
+name = "roff"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b"
-dependencies = [
- "cc",
- "getrandom",
- "libc",
- "spin 0.9.8",
- "untrusted 0.9.0",
- "windows-sys 0.48.0",
-]
+checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"
[[package]]
name = "rsa"
-version = "0.9.3"
+version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86ef35bf3e7fe15a53c4ab08a998e42271eab13eb0db224126bc7bc4c4bad96d"
+checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc"
dependencies = [
"const-oid",
"digest",
@@ -2739,9 +3218,9 @@ dependencies = [
[[package]]
name = "rtnetlink"
-version = "0.13.1"
+version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a552eb82d19f38c3beed3f786bd23aa434ceb9ac43ab44419ca6d67a7e186c0"
+checksum = "b684475344d8df1859ddb2d395dd3dac4f8f3422a1aa0725993cb375fc5caba5"
dependencies = [
"futures",
"log",
@@ -2750,7 +3229,7 @@ dependencies = [
"netlink-packet-utils",
"netlink-proto",
"netlink-sys",
- "nix 0.26.4",
+ "nix 0.27.1",
"thiserror",
"tokio",
]
@@ -2770,28 +3249,50 @@ dependencies = [
"semver",
]
+[[package]]
+name = "rustdoc-json"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a21e727474edad247ba206da91e5d3f76c0ff4a4ddac1fb7bba508df49101388"
+dependencies = [
+ "cargo-manifest",
+ "cargo_metadata",
+ "serde",
+ "thiserror",
+ "toml",
+]
+
+[[package]]
+name = "rustdoc-types"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc646f09069d689083b975698bdac4edb96c2f0e7d270b701760814b886bb224"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "rustix"
-version = "0.38.24"
+version = "0.38.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ad981d6c340a49cdc40a1028d9c6084ec7e9fa33fcb839cab656a267071e234"
+checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.5.0",
"errno",
"libc",
"linux-raw-sys",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
]
[[package]]
name = "rustls"
-version = "0.21.8"
+version = "0.21.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c"
+checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
dependencies = [
"log",
- "ring 0.17.5",
- "rustls-webpki",
+ "ring",
+ "rustls-webpki 0.101.7",
"sct",
]
@@ -2801,30 +3302,62 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
- "base64 0.21.5",
+ "base64 0.21.7",
]
+[[package]]
+name = "rustls-pki-types"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247"
+
[[package]]
name = "rustls-webpki"
version = "0.101.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
dependencies = [
- "ring 0.17.5",
- "untrusted 0.9.0",
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.102.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustup-toolchain"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64406f60467f189c326fc2e7f74ee643c28d1ff500068e0fa0f5e56a04e2cc95"
+dependencies = [
+ "thiserror",
]
[[package]]
name = "rustversion"
-version = "1.0.14"
+version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
+checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47"
[[package]]
name = "ryu"
-version = "1.0.15"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
+
+[[package]]
+name = "ryu-js"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+checksum = "6518fc26bced4d53678a22d6e423e9d8716377def84545fe328236e3af070e7f"
[[package]]
name = "salsa20"
@@ -2844,6 +3377,58 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "schannel"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "schemafy"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9725c16a64e85972fcb3630677be83fef699a1cd8e4bfbdcf3b3c6675f838a19"
+dependencies = [
+ "Inflector",
+ "schemafy_core",
+ "schemafy_lib",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "serde_repr",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "schemafy_core"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bec29dddcfe60f92f3c0d422707b8b56473983ef0481df8d5236ed3ab8fdf24"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemafy_lib"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af3d87f1df246a9b7e2bfd1f4ee5f88e48b11ef9cfc62e63f0dead255b1a6f5f"
+dependencies = [
+ "Inflector",
+ "proc-macro2",
+ "quote",
+ "schemafy_core",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "syn 1.0.109",
+ "uriparse",
+]
+
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -2868,8 +3453,8 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
dependencies = [
- "ring 0.17.5",
- "untrusted 0.9.0",
+ "ring",
+ "untrusted",
]
[[package]]
@@ -2887,45 +3472,62 @@ dependencies = [
]
[[package]]
-name = "semver"
-version = "1.0.20"
+name = "security-framework"
+version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
+checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
[[package]]
-name = "serde"
-version = "1.0.193"
+name = "security-framework-sys"
+version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
+checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef"
dependencies = [
- "serde_derive",
+ "core-foundation-sys",
+ "libc",
]
[[package]]
-name = "serde_bytes"
-version = "0.11.12"
+name = "semver"
+version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff"
+checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
dependencies = [
"serde",
]
+[[package]]
+name = "serde"
+version = "1.0.203"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
+dependencies = [
+ "serde_derive",
+]
+
[[package]]
name = "serde_derive"
-version = "1.0.193"
+version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
+checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.58",
]
[[package]]
name = "serde_json"
-version = "1.0.108"
+version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
+checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
dependencies = [
"itoa",
"ryu",
@@ -2941,11 +3543,22 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serde_repr"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.58",
+]
+
[[package]]
name = "serde_spanned"
-version = "0.6.4"
+version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80"
+checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
dependencies = [
"serde",
]
@@ -2962,6 +3575,36 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serde_with"
+version = "3.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a"
+dependencies = [
+ "base64 0.21.7",
+ "chrono",
+ "hex",
+ "indexmap 1.9.3",
+ "indexmap 2.2.6",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "serde_with_macros",
+ "time",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "3.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655"
+dependencies = [
+ "darling 0.20.8",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.58",
+]
+
[[package]]
name = "sha1"
version = "0.10.6"
@@ -2985,14 +3628,10 @@ dependencies = [
]
[[package]]
-name = "sha3"
-version = "0.10.8"
+name = "shell-words"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
-dependencies = [
- "digest",
- "keccak",
-]
+checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]]
name = "signal-hook-registry"
@@ -3005,9 +3644,9 @@ dependencies = [
[[package]]
name = "signature"
-version = "2.1.0"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"digest",
"rand_core",
@@ -3015,12 +3654,12 @@ dependencies = [
[[package]]
name = "sigstore"
-version = "0.7.2"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a306742205ee5e287f0c0cbb8f8361f6eda60a67232860c9e285778f001680fa"
+checksum = "86debd0b4c8afb24efa09179ccba9da5daf9bbc7a4b786f3fb7ea26214bdfaf7"
dependencies = [
"async-trait",
- "base64 0.21.5",
+ "base64 0.22.1",
"cached",
"cfg-if",
"chrono",
@@ -3031,26 +3670,34 @@ dependencies = [
"ed25519",
"ed25519-dalek",
"elliptic-curve",
+ "futures",
+ "futures-util",
"getrandom",
+ "hex",
+ "json-syntax",
"lazy_static",
"oci-distribution",
"olpc-cjson",
"p256",
"p384",
- "pem 2.0.1",
- "picky",
+ "pem",
"pkcs1",
"pkcs8",
"rand",
"regex",
"rsa",
+ "rustls-webpki 0.102.2",
"scrypt",
"serde",
"serde_json",
+ "serde_repr",
+ "serde_with",
"sha2",
"signature",
+ "sigstore_protobuf_specs",
"thiserror",
"tokio",
+ "tokio-util",
"tough",
"tracing",
"url",
@@ -3060,68 +3707,91 @@ dependencies = [
]
[[package]]
-name = "slab"
-version = "0.4.9"
+name = "sigstore_protobuf_specs"
+version = "0.1.0-rc.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c54c3284a3ed53bd585dfbbe80b81142ad35128d7cba817623c4e066a4a95a2b"
+dependencies = [
+ "schemafy",
+ "schemafy_core",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "sled"
+version = "0.34.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
+dependencies = [
+ "crc32fast",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+ "fs2",
+ "fxhash",
+ "libc",
+ "log",
+ "parking_lot 0.11.2",
+]
+
+[[package]]
+name = "smallstr"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+checksum = "63b1aefdf380735ff8ded0b15f31aab05daf1f70216c01c02a12926badd1df9d"
dependencies = [
- "autocfg",
+ "serde",
+ "smallvec",
]
[[package]]
name = "smallvec"
-version = "1.11.2"
+version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "snafu"
-version = "0.7.5"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6"
+checksum = "75976f4748ab44f6e5332102be424e7c2dc18daeaf7e725f2040c3ebb133512e"
dependencies = [
- "doc-comment",
+ "futures-core",
+ "pin-project",
"snafu-derive",
]
[[package]]
name = "snafu-derive"
-version = "0.7.5"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf"
+checksum = "b4b19911debfb8c2fb1107bc6cb2d61868aaf53a988449213959bb1b5b1ed95f"
dependencies = [
- "heck",
+ "heck 0.4.1",
"proc-macro2",
"quote",
- "syn 1.0.109",
+ "syn 2.0.58",
]
[[package]]
name = "socket2"
-version = "0.4.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
-dependencies = [
- "libc",
- "winapi",
-]
-
-[[package]]
-name = "socket2"
-version = "0.5.5"
+version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
+checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871"
dependencies = [
"libc",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
]
-[[package]]
-name = "spin"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
-
[[package]]
name = "spin"
version = "0.9.8"
@@ -3130,37 +3800,49 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "spki"
-version = "0.7.2"
+version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der",
]
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
[[package]]
name = "strum"
-version = "0.25.0"
+version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
+checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
[[package]]
name = "strum_macros"
-version = "0.25.3"
+version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
+checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946"
dependencies = [
- "heck",
+ "heck 0.4.1",
"proc-macro2",
"quote",
"rustversion",
- "syn 2.0.39",
+ "syn 2.0.58",
]
[[package]]
@@ -3182,9 +3864,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.39"
+version = "2.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
+checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
dependencies = [
"proc-macro2",
"quote",
@@ -3220,19 +3902,19 @@ dependencies = [
[[package]]
name = "systemd-journal-logger"
-version = "1.0.0"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "356b5cb52ce54916cbfaee19b07d305c7ea8ce5435a088c58743d4a0211f3eff"
+checksum = "b5f3848dd723f2a54ac1d96da793b32923b52de8dfcced8722516dac312a5b2a"
dependencies = [
- "libsystemd",
"log",
+ "rustix",
]
[[package]]
name = "tar"
-version = "0.4.40"
+version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb"
+checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909"
dependencies = [
"filetime",
"libc",
@@ -3240,15 +3922,14 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.8.1"
+version = "3.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
+checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
dependencies = [
"cfg-if",
"fastrand",
- "redox_syscall 0.4.1",
"rustix",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
]
[[package]]
@@ -3259,22 +3940,53 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
[[package]]
name = "thiserror"
-version = "1.0.50"
+version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
+checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.50"
+version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
+checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.58",
+]
+
+[[package]]
+name = "time"
+version = "0.3.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef89ece63debf11bc32d1ed8d078ac870cbeb44da02afb02a9ff135ae7ca0582"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "time-macros"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
+dependencies = [
+ "num-conv",
+ "time-core",
]
[[package]]
@@ -3292,21 +4004,42 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+[[package]]
+name = "tls_codec"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e78c9c330f8c85b2bae7c8368f2739157db9991235123aa1b15ef9502bfb6a"
+dependencies = [
+ "tls_codec_derive",
+ "zeroize",
+]
+
+[[package]]
+name = "tls_codec_derive"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.58",
+]
+
[[package]]
name = "tokio"
-version = "1.34.0"
+version = "1.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
+checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"num_cpus",
- "parking_lot",
+ "parking_lot 0.12.1",
"pin-project-lite",
"signal-hook-registry",
- "socket2 0.5.5",
+ "socket2",
"tokio-macros",
"windows-sys 0.48.0",
]
@@ -3323,13 +4056,23 @@ dependencies = [
[[package]]
name = "tokio-macros"
-version = "2.2.0"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
+checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.58",
+]
+
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
]
[[package]]
@@ -3344,9 +4087,9 @@ dependencies = [
[[package]]
name = "tokio-stream"
-version = "0.1.14"
+version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
+checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
dependencies = [
"futures-core",
"pin-project-lite",
@@ -3355,25 +4098,24 @@ dependencies = [
[[package]]
name = "tokio-util"
-version = "0.7.10"
+version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
+checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
dependencies = [
"bytes",
"futures-core",
- "futures-io",
"futures-sink",
"pin-project-lite",
"tokio",
- "tracing",
]
[[package]]
name = "toml"
-version = "0.7.8"
+version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
+checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
dependencies = [
+ "indexmap 2.2.6",
"serde",
"serde_spanned",
"toml_datetime",
@@ -3382,20 +4124,20 @@ dependencies = [
[[package]]
name = "toml_datetime"
-version = "0.6.5"
+version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
+checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
-version = "0.19.15"
+version = "0.22.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
dependencies = [
- "indexmap 2.1.0",
+ "indexmap 2.2.6",
"serde",
"serde_spanned",
"toml_datetime",
@@ -3404,42 +4146,14 @@ dependencies = [
[[package]]
name = "tonic"
-version = "0.9.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a"
-dependencies = [
- "async-trait",
- "axum",
- "base64 0.21.5",
- "bytes",
- "futures-core",
- "futures-util",
- "h2",
- "http",
- "http-body",
- "hyper",
- "hyper-timeout",
- "percent-encoding",
- "pin-project",
- "prost 0.11.9",
- "tokio",
- "tokio-stream",
- "tower",
- "tower-layer",
- "tower-service",
- "tracing",
-]
-
-[[package]]
-name = "tonic"
-version = "0.10.2"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e"
+checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13"
dependencies = [
"async-stream",
"async-trait",
"axum",
- "base64 0.21.5",
+ "base64 0.21.7",
"bytes",
"h2",
"http",
@@ -3448,7 +4162,7 @@ dependencies = [
"hyper-timeout",
"percent-encoding",
"pin-project",
- "prost 0.12.3",
+ "prost",
"tokio",
"tokio-stream",
"tower",
@@ -3459,40 +4173,47 @@ dependencies = [
[[package]]
name = "tonic-build"
-version = "0.10.2"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d021fc044c18582b9a2408cd0dd05b1596e3ecdb5c4df822bb0183545683889"
+checksum = "be4ef6dd70a610078cb4e338a0f79d06bc759ff1b22d2120c2ff02ae264ba9c2"
dependencies = [
"prettyplease",
"proc-macro2",
"prost-build",
"quote",
- "syn 2.0.39",
+ "syn 2.0.58",
]
[[package]]
name = "tough"
-version = "0.13.0"
+version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c259b2bd13fdff3305a5a92b45befb1adb315d664612c8991be57fb6a83dc126"
+checksum = "b8d7a87d51ca5a113542e1b9f5ee2b14b6864bf7f34d103740086fa9c3d57d3b"
dependencies = [
+ "async-recursion",
+ "async-trait",
+ "bytes",
"chrono",
"dyn-clone",
+ "futures",
+ "futures-core",
"globset",
"hex",
"log",
"olpc-cjson",
- "path-absolutize",
- "pem 1.1.1",
+ "pem",
"percent-encoding",
"reqwest",
- "ring 0.16.20",
+ "ring",
"serde",
"serde_json",
"serde_plain",
"snafu",
"tempfile",
- "untrusted 0.7.1",
+ "tokio",
+ "tokio-util",
+ "typed-path",
+ "untrusted",
"url",
"walkdir",
]
@@ -3549,7 +4270,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.58",
]
[[package]]
@@ -3562,56 +4283,16 @@ dependencies = [
]
[[package]]
-name = "trust-dns-proto"
-version = "0.23.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374"
-dependencies = [
- "async-trait",
- "cfg-if",
- "data-encoding",
- "enum-as-inner",
- "futures-channel",
- "futures-io",
- "futures-util",
- "idna 0.4.0",
- "ipnet",
- "once_cell",
- "rand",
- "smallvec",
- "thiserror",
- "tinyvec",
- "tokio",
- "tracing",
- "url",
-]
-
-[[package]]
-name = "trust-dns-resolver"
-version = "0.23.2"
+name = "try-lock"
+version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10a3e6c3aff1718b3c73e395d1f35202ba2ffa847c6a62eea0db8fb4cfe30be6"
-dependencies = [
- "cfg-if",
- "futures-util",
- "ipconfig",
- "lru-cache",
- "once_cell",
- "parking_lot",
- "rand",
- "resolv-conf",
- "smallvec",
- "thiserror",
- "tokio",
- "tracing",
- "trust-dns-proto",
-]
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
-name = "try-lock"
-version = "0.2.4"
+name = "typed-path"
+version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
+checksum = "668404597c2c687647f6f8934f97c280fd500db28557f52b07c56b92d3dc500a"
[[package]]
name = "typenum"
@@ -3630,9 +4311,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
-version = "0.3.13"
+version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
+checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
[[package]]
name = "unicode-ident"
@@ -3642,9 +4323,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-normalization"
-version = "0.1.22"
+version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
dependencies = [
"tinyvec",
]
@@ -3667,21 +4348,25 @@ dependencies = [
[[package]]
name = "untrusted"
-version = "0.7.1"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
-name = "untrusted"
-version = "0.9.0"
+name = "uriparse"
+version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff"
+dependencies = [
+ "fnv",
+ "lazy_static",
+]
[[package]]
name = "url"
-version = "2.5.0"
+version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
+checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
dependencies = [
"form_urlencoded",
"idna 0.5.0",
@@ -3695,13 +4380,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
-name = "users"
-version = "0.11.0"
+name = "utf8-decode"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
-dependencies = [
- "libc",
-]
+checksum = "ca61eb27fa339aa08826a29f03e87b99b4d8f0fc2255306fd266bb1b6a9de498"
[[package]]
name = "utf8parse"
@@ -3711,18 +4393,18 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uuid"
-version = "1.5.0"
+version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
+checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
dependencies = [
"serde",
]
[[package]]
name = "value-bag"
-version = "1.4.2"
+version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a72e1902dde2bd6441347de2b70b7f5d59bf157c6c62f0c44572607a1d55bbe"
+checksum = "74797339c3b98616c009c7c3eb53a0ce41e85c8ec66bd3db96ed132d20cfdee8"
[[package]]
name = "vcpkg"
@@ -3747,9 +4429,9 @@ dependencies = [
[[package]]
name = "walkdir"
-version = "2.4.0"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
@@ -3772,9 +4454,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
-version = "0.2.88"
+version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@@ -3782,24 +4464,24 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.88"
+version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.58",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.38"
+version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02"
+checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
dependencies = [
"cfg-if",
"js-sys",
@@ -3809,9 +4491,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.88"
+version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -3819,28 +4501,28 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.88"
+version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.58",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.88"
+version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "wasm-streams"
-version = "0.3.0"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7"
+checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129"
dependencies = [
"futures-util",
"js-sys",
@@ -3851,9 +4533,9 @@ dependencies = [
[[package]]
name = "web-sys"
-version = "0.3.65"
+version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85"
+checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -3861,9 +4543,9 @@ dependencies = [
[[package]]
name = "webbrowser"
-version = "0.8.12"
+version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82b2391658b02c27719fc5a0a73d6e696285138e8b12fba9d4baa70451023c71"
+checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b"
dependencies = [
"core-foundation",
"home",
@@ -3878,27 +4560,15 @@ dependencies = [
[[package]]
name = "webpki-roots"
-version = "0.25.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
-
-[[package]]
-name = "which"
-version = "4.4.2"
+version = "0.25.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
-dependencies = [
- "either",
- "home",
- "once_cell",
- "rustix",
-]
+checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
[[package]]
name = "widestring"
-version = "1.0.2"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8"
+checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
[[package]]
name = "winapi"
@@ -3933,11 +4603,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
-version = "0.51.1"
+version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
- "windows-targets 0.48.5",
+ "windows-targets 0.52.4",
]
[[package]]
@@ -3958,6 +4628,15 @@ dependencies = [
"windows-targets 0.48.5",
]
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.4",
+]
+
[[package]]
name = "windows-targets"
version = "0.42.2"
@@ -3988,6 +4667,21 @@ dependencies = [
"windows_x86_64_msvc 0.48.5",
]
+[[package]]
+name = "windows-targets"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.4",
+ "windows_aarch64_msvc 0.52.4",
+ "windows_i686_gnu 0.52.4",
+ "windows_i686_msvc 0.52.4",
+ "windows_x86_64_gnu 0.52.4",
+ "windows_x86_64_gnullvm 0.52.4",
+ "windows_x86_64_msvc 0.52.4",
+]
+
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
@@ -4000,6 +4694,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
+
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
@@ -4012,6 +4712,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
+
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
@@ -4024,6 +4730,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
+
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
@@ -4036,6 +4748,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
+
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
@@ -4048,6 +4766,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
+
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
@@ -4060,6 +4784,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
+
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
@@ -4072,11 +4802,17 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
+
[[package]]
name = "winnow"
-version = "0.5.19"
+version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b"
+checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8"
dependencies = [
"memchr",
]
@@ -4091,27 +4827,18 @@ dependencies = [
"windows-sys 0.48.0",
]
-[[package]]
-name = "x25519-dalek"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96"
-dependencies = [
- "curve25519-dalek",
- "rand_core",
- "serde",
- "zeroize",
-]
-
[[package]]
name = "x509-cert"
-version = "0.2.4"
+version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25eefca1d99701da3a57feb07e5079fc62abba059fc139e98c13bbb250f3ef29"
+checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94"
dependencies = [
"const-oid",
"der",
+ "sha1",
+ "signature",
"spki",
+ "tls_codec",
]
[[package]]
@@ -4119,37 +4846,48 @@ name = "xtask"
version = "0.1.0"
dependencies = [
"anyhow",
+ "bpfman",
+ "bpfman-api",
+ "cargo_metadata",
"clap",
+ "clap_complete",
+ "clap_mangen",
+ "dialoguer",
+ "diff",
+ "hex",
"lazy_static",
+ "public-api",
+ "rustdoc-json",
+ "rustup-toolchain",
"serde_json",
"tonic-build",
]
[[package]]
name = "zerocopy"
-version = "0.7.26"
+version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0"
+checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.7.26"
+version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f"
+checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.58",
]
[[package]]
name = "zeroize"
-version = "1.6.1"
+version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12a3946ecfc929b583800f4629b6c25b88ac6e92a40ea5670f77112a85d40a8b"
+checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
dependencies = [
"zeroize_derive",
]
@@ -4162,5 +4900,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.39",
+ "syn 2.0.58",
]
diff --git a/Cargo.toml b/Cargo.toml
index 2825f6795..f575ea933 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,8 +1,10 @@
[workspace]
members = [
+ "bpf-log-exporter",
"bpf-metrics-exporter",
"bpfman",
"bpfman-api",
+ "bpfman-ns",
"csi",
"tests/integration-test",
"tests/integration-test-macros",
@@ -10,60 +12,89 @@ members = [
]
resolver = "2"
-[patch.crates-io]
-aya = { git = "https://github.com/aya-rs/aya", branch = "main" }
+[workspace.package]
+documentation = "https://docs.bpfman.io"
+edition = "2021"
+homepage = "https://bpfman.io"
+license = "Apache-2.0"
+repository = "https://github.com/bpfman/bpfman"
+version = "0.4.0"
[workspace.dependencies]
anyhow = { version = "1", default-features = false }
assert_cmd = { version = "2", default-features = false }
assert_matches = { version = "1", default-features = false }
async-trait = { version = "0.1", default-features = false }
-aya = { version = "0.11", default-features = false }
+aya = { version = "0.12", default-features = false }
base16ct = { version = "0.2.0", default-features = false }
-base64 = { version = "0.21.5", default-features = false }
-bpfman-api = { version = "0.3.1", path = "./bpfman-api" }
+base64 = { version = "0.22.1", default-features = false }
+bpfman = { version = "0.4.0", path = "./bpfman" }
+bpfman-api = { version = "0.4.0", path = "./bpfman-api" }
bpfman-csi = { version = "1.8.0", path = "./csi" }
caps = { version = "0.5.4", default-features = false }
-chrono = { version = "0.4.31", default-features = false }
+cargo_metadata = { version = "0.18.0", default-features = false }
+chrono = { version = "0.4.38", default-features = false }
clap = { version = "4", default-features = false }
-comfy-table = { version = "7.1.0", default-features = false }
-env_logger = { version = "0.10", default-features = false }
+clap_complete = { version = "4.5.6", default-features = false }
+clap_mangen = { version = "0.2.21", default-features = false }
+comfy-table = { version = "7.1.1", default-features = false }
+dialoguer = { version = "0.11", default-features = false }
+diff = { version = "0.1.13", default-features = false }
+env_logger = { version = "0.11.3", default-features = false }
flate2 = { version = "1.0", default-features = false }
-futures = { version = "0.3.29", default-features = false }
+futures = { version = "0.3.30", default-features = false }
hex = { version = "0.4.3", default-features = false }
integration-test-macros = { path = "./tests/integration-test-macros" }
inventory = { version = "0.3", default-features = false }
lazy_static = { version = "1", default-features = false }
+libsystemd = { version = "0.7.0", default-features = false }
log = { version = "0.4", default-features = false }
-netlink-packet-route = { version = "0.17.1", default-features = false }
-nix = { version = "0.27", default-features = false }
-oci-distribution = { version = "0.9", default-features = false }
-opentelemetry = { version = "0.21.0", default-features = false }
-opentelemetry-otlp = { version = "0.14.0", default-features = false }
-opentelemetry-semantic-conventions = { version = "0.13.0" }
-opentelemetry_sdk = { version = "0.21.1", default-features = false }
+netlink-packet-audit = { version = "^0.5", default-features = false }
+netlink-packet-core = { version = "^0.7", default-features = false }
+netlink-packet-route = { version = "^0.19", default-features = false }
+netlink-sys = { version = "^0.8", default-features = false }
+nix = { version = "0.28", default-features = false }
+oci-distribution = { version = "0.10", default-features = false }
+opentelemetry = { version = "0.22.0", default-features = false }
+opentelemetry-otlp = { version = "0.15.0", default-features = false }
+opentelemetry-semantic-conventions = { version = "0.14.0" }
+opentelemetry_sdk = { version = "0.22.1", default-features = false }
predicates = { version = "3.0.4", default-features = false }
-prost = { version = "0.12.3", default-features = false }
-prost-types = { version = "0.12.2", default-features = false }
+prost = { version = "0.12.6", default-features = false }
+prost-types = { version = "0.12.6", default-features = false }
+public-api = { version = "0.33.1", default-features = false }
quote = { version = "1", default-features = false }
rand = { version = "0.8", default-features = false }
-regex = { version = "1.9.6", default-features = false }
-rtnetlink = { version = "0.13.1", default-features = false }
+regex = { version = "1.10.5", default-features = false }
+rtnetlink = { version = "0.14", default-features = false }
+rustdoc-json = { version = "0.8.9", default-features = false }
+rustup-toolchain = { version = "0.1.6", default-features = false }
serde = { version = "1.0", default-features = false }
serde_json = { version = "1", default-features = false }
sha2 = { version = "0.10.8", default-features = false }
-sigstore = { version = "0.7.2", default-features = false }
+sigstore = { version = "0.9.0", default-features = false }
+sled = { version = "0.34.7", default-features = false }
syn = { version = "2.0", default-features = false }
-systemd-journal-logger = { version = "1.0.0", default-features = false }
+systemd-journal-logger = { version = "2.1.1", default-features = false }
tar = { version = "0.4", default-features = false }
-tempfile = { version = "3.8.1", default-features = false }
+tempfile = { version = "3.10.1", default-features = false }
thiserror = { version = "1", default-features = false }
-tokio = { version = "1.34.0", default-features = false }
-tokio-stream = { version = "0.1.12", default-features = false }
-tokio-util = { version = "0.7.10", default-features = false }
-toml = { version = "0.7", default-features = false }
-tonic = { version = "0.10.2", default-features = false }
-tonic-build = { version = "0.10.2", default-features = false }
+tokio = { version = "1.38.0", default-features = false }
+tokio-stream = { version = "0.1.15", default-features = false }
+tokio-util = { version = "0.7.11", default-features = false }
+toml = { version = "0.8.14", default-features = false }
+tonic = { version = "0.11.0", default-features = false }
+tonic-build = { version = "0.11.0", default-features = false }
tower = { version = "0.4.13", default-features = false }
-url = { version = "2.5.0", default-features = false }
+url = { version = "2.5.2", default-features = false }
users = { version = "0.11.0", default-features = false }
+
+
+[workspace.metadata.vendor-filter]
+platforms = [
+ "aarch64-unknown-linux-gnu",
+ "i686-unknown-linux-gnu",
+ "powerpc64le-unknown-linux-gnu",
+ "s390x-unknown-linux-gnu",
+ "x86_64-unknown-linux-gnu",
+]
diff --git a/Containerfile.bpfman b/Containerfile.bpfman
index 2a10b5d40..320e9eb12 100644
--- a/Containerfile.bpfman
+++ b/Containerfile.bpfman
@@ -8,20 +8,20 @@ RUN apt-get update && apt-get install -y\
protobuf-compiler\
libelf-dev\
gcc-multilib\
- musl-tools
+ libssl-dev
WORKDIR /usr/src/bpfman
COPY ./ /usr/src/bpfman
-RUN rustup target add x86_64-unknown-linux-musl
-
# Compile only the C ebpf bytecode
RUN cargo xtask build-ebpf --release --libbpf-dir /usr/src/bpfman/libbpf
-# Compile only bpfman
-RUN cargo build --release -p bpfman --target x86_64-unknown-linux-musl
+# Compile bpfman cli, bpfman-ns, and bpfman-rpc binaries
+RUN cargo build --release
-FROM scratch
+FROM redhat/ubi9-minimal
-COPY --from=bpfman-build /usr/src/bpfman/target/x86_64-unknown-linux-musl/release/bpfman .
+COPY --from=bpfman-build /usr/src/bpfman/target/release/bpfman .
+COPY --from=bpfman-build /usr/src/bpfman/target/release/bpfman-ns .
+COPY --from=bpfman-build /usr/src/bpfman/target/release/bpfman-rpc .
-ENTRYPOINT ["./bpfman"]
+ENTRYPOINT ["./bpfman-rpc", "--timeout=0"]
diff --git a/Containerfile.bpfman.local b/Containerfile.bpfman.local
index d10184266..13a68e56d 100644
--- a/Containerfile.bpfman.local
+++ b/Containerfile.bpfman.local
@@ -2,39 +2,32 @@
## builds, dramatically speeding up the local development process.
FROM rust:1 as bpfman-build
-RUN git clone https://github.com/libbpf/libbpf --branch v0.8.0 /usr/src/bpfman/libbpf
-
RUN apt-get update && apt-get install -y\
- git\
- clang\
- protobuf-compiler\
- libelf-dev\
- gcc-multilib\
- musl-tools
+ gcc-multilib\
+ libssl-dev
WORKDIR /usr/src/bpfman
COPY ./ /usr/src/bpfman
-RUN rustup target add x86_64-unknown-linux-musl
-
-# Compile only the C ebpf bytecode
+# Compile bpfman cli, bpfman-ns, and bpfman-rpc binaries
RUN --mount=type=cache,target=/usr/src/bpfman/target/ \
--mount=type=cache,target=/usr/local/cargo/registry \
- cargo xtask build-ebpf --release --libbpf-dir /usr/src/bpfman/libbpf
+ cargo build --release
-# Compile only bpfman
RUN --mount=type=cache,target=/usr/src/bpfman/target/ \
- --mount=type=cache,target=/usr/local/cargo/registry \
- cargo build --release --target x86_64-unknown-linux-musl
+ cp /usr/src/bpfman/target/release/bpfman ./bpfman/
+
+RUN --mount=type=cache,target=/usr/src/bpfman/target/ \
+ cp /usr/src/bpfman/target/release/bpfman-ns ./bpfman/
RUN --mount=type=cache,target=/usr/src/bpfman/target/ \
- cp /usr/src/bpfman/target/x86_64-unknown-linux-musl/release/bpfman ./bpfman/
+ cp /usr/src/bpfman/target/release/bpfman-rpc ./bpfman/
## Image for Local testing is much more of a debug image, give it bpftool and tcpdump
-FROM fedora:38
+FROM fedora:39
RUN dnf makecache --refresh && dnf -y install bpftool tcpdump
COPY --from=bpfman-build ./usr/src/bpfman/bpfman .
-ENTRYPOINT ["./bpfman", "system", "service"]
+ENTRYPOINT ["./bpfman-rpc", "--timeout=0"]
diff --git a/MAINTAINERS.md b/MAINTAINERS.md
index 775efc2d3..214fa3d31 100644
--- a/MAINTAINERS.md
+++ b/MAINTAINERS.md
@@ -2,7 +2,7 @@
See [CONTRIBUTING.md](./CONTRIBUTING.md) for general contribution guidelines.
See [GOVERNANCE.md](./GOVERNANCE.md) for governance guidelines and maintainer responsibilities.
-See [CODEOWNERS](./CODEOWNERS) for a detailed list of owners for the various source directories.
+See [CODEOWNERS](https://github.com/bpfman/bpfman/blob/main/CODEOWNERS) for a detailed list of owners for the various source directories.
| Name | Employer | Responsibilities |
| ---- | -------- | ---------------- |
diff --git a/README.md b/README.md
index a14195163..fdf065c8f 100644
--- a/README.md
+++ b/README.md
@@ -1,39 +1,58 @@
-# bpfman: An eBPF Manager
+
-[](https://opensource.org/licenses/Apache-2.0)
-[](https://opensource.org/licenses/BSD-2-Clause)
-[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
-![Build status][build-badge] [![Book][book-badge]][book-url]
-[](https://app.netlify.com/sites/bpfman/deploys)
+# bpfman: An eBPF Manager
-[build-badge]:
- https://img.shields.io/github/actions/workflow/status/bpfman/bpfman/build.yml?branch=main
+[![License][apache2-badge]][apache2-url]
+[![License][bsd2-badge]][bsd2-url]
+[![License][gpl-badge]][gpl-url]
+![Build status][build-badge]
+[![Book][book-badge]][book-url]
+[![Netlify Status][netlify-badge]][netlify-url]
+[![Copr build status][copr-badge]][copr-url]
+
+[apache2-badge]: https://img.shields.io/badge/License-Apache%202.0-blue.svg
+[apache2-url]: https://opensource.org/licenses/Apache-2.0
+[bsd2-badge]: https://img.shields.io/badge/License-BSD%202--Clause-orange.svg
+[bsd2-url]: https://opensource.org/licenses/BSD-2-Clause
+[gpl-badge]: https://img.shields.io/badge/License-GPL%20v2-blue.svg
+[gpl-url]: https://opensource.org/licenses/GPL-2.0
+[build-badge]: https://img.shields.io/github/actions/workflow/status/bpfman/bpfman/build.yml?branch=main
[book-badge]: https://img.shields.io/badge/read%20the-book-9cf.svg
[book-url]: https://bpfman.io/
+[copr-badge]: https://copr.fedorainfracloud.org/coprs/g/ebpf-sig/bpfman-next/package/bpfman/status_image/last_build.png
+[copr-url]: https://copr.fedorainfracloud.org/coprs/g/ebpf-sig/bpfman-next/package/bpfman/
+[netlify-badge]: https://api.netlify.com/api/v1/badges/557ca612-4b7f-480d-a1cc-43b453502992/deploy-status
+[netlify-url]: https://app.netlify.com/sites/bpfman/deploys
_Formerly know as `bpfd`_
## Welcome to bpfman
-bpfman is a system daemon aimed at simplifying the deployment and management of eBPF programs.
-It's goal is to enhance the developer-experience as well as provide features to improve security,
-visibility and program-cooperation.
-bpfman includes a Kubernetes operator to bring those same features to Kubernetes, allowing users to
-safely deploy eBPF via custom resources across nodes in a cluster.
+bpfman operates as an eBPF manager, focusing on simplifying the deployment and administration of eBPF programs. Its notable features encompass:
+
+- **System Overview**: Provides insights into how eBPF is utilized in your system.
+- **eBPF Program Loader**: Includes a built-in program loader that supports program cooperation for XDP and TC programs, as well as deployment of eBPF programs from OCI images.
+- **eBPF Filesystem Management**: Manages the eBPF filesystem, facilitating the deployment of eBPF applications without requiring additional privileges.
+
+Our program loader and eBPF filesystem manager ensure the secure deployment of eBPF applications.
+Furthermore, bpfman includes a Kubernetes operator, extending these capabilities to Kubernetes.
+This allows users to confidently deploy eBPF through custom resource definitions across nodes in a cluster.
Here are some links to help in your bpfman journey (all links are from the bpfman website ):
- [Welcome to bpfman](https://bpfman.io/) for overview of bpfman.
-- [Setup and Building bpfman](https://bpfman.io/getting-started/building-bpfman/) for
- instructions on setting up your development environment and building bpfman.
-- [Tutorial](https://bpfman.io/getting-started/tutorial/) for some examples of starting
- `bpfman`, managing logs, and using the CLI.
-- [Example eBPF Programs](https://bpfman.io/getting-started/example-bpf/) for some
+- [Deploying Example eBPF Programs On Local Host](https://bpfman.io/main/getting-started/example-bpf-local/)
+ for some examples of running `bpfman` on local host and using the CLI to install
+ eBPF programs on the host.
+- [Deploying Example eBPF Programs On Kubernetes](https://bpfman.io/main/getting-started/example-bpf-k8s/)
+ for some examples of deploying eBPF programs through `bpfman` in a Kubernetes deployment.
+- [Setup and Building bpfman](https://bpfman.io/main/getting-started/building-bpfman/) for instructions
+ on setting up your development environment and building bpfman.
+- [Example eBPF Programs](https://bpfman.io/main/getting-started/example-bpf/) for some
examples of eBPF programs written in Go, interacting with `bpfman`.
-- [How to Deploy bpfman on Kubernetes](https://bpfman.io/developer-guide/develop-operator/) for details on launching
+- [Deploying the bpfman-operator](https://bpfman.io/main/developer-guide/develop-operator/) for details on launching
bpfman in a Kubernetes cluster.
-- [Meet the Community](https://bpfman.io/governance/meetings/) for details on community meeting details.
+- [Meet the Community](https://bpfman.io/main/governance/meetings/) for details on community meeting details.
## License
@@ -65,3 +84,4 @@ dual licensed as above, without any additional terms or conditions.
[GNU General Public License, Version 2]: LICENSE-GPL2
[BSD 2 Clause]: LICENSE-BSD2
[libxdp]: https://github.com/xdp-project/xdp-tools
+[TC dispatcher]:https://github.com/bpfman/bpfman/blob/main/bpf/tc_dispatcher.bpf.c
diff --git a/RELEASE.md b/RELEASE.md
index e5bcc20fe..72884decb 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -1,49 +1,65 @@
# Release Process
+This document describes how to cut a release for the bpfman project.
+
## Overview
A release for the bpfman project is comprised of the following major components:
-- bpfman binaries
-- Core GRPC API protobuf definitions
-- Kubernetes Custom Resource Definitions (CRDs)
-- Corresponding go pkg in the form of `github.com/bpfman/bpfman` which includes the following:
- - `github.com/bpfman/bpfman/clients/gobpfman/v1`: The go client for the bpfman GRPC API
- - `github.com/bpfman/bpfman/bpfman-operator/apis`: The go bindings for the
- bpfman CRD API
- - `github.com/bpfman/bpfman/bpfman-operator/pkg/client`: The autogenerated
- clientset for the bpfman CRD API
- - `github.com/bpfman/bpfman/bpfman-operator/pkg/helpers`: The provided bpfman CRD
- API helpers.
-- Corresponding `bpfman-api` and `bpfman`rust crates which house the rust client for the bpfman GRPC API
+- bpfman (Core library) and bpfman-api (Core GRPC API protobuf definitions) library crates
+- bpfman (CLI), and bpfman-rpc ( gRPC server ) binary crates
+- bpf-metrics-exporter and bpf-log-exporter binary crates
+- Kubernetes User Facing Custom Resource Definitions (CRDs)
+ - `TcProgram`
+ - `XdpProgram`
+ - `TracepointProgram`
+ - `UprobeProgram`
+ - `KprobeProgram`
+ - `FentryProgram`
+ - `FexitProgram`
+- Corresponding go pkgs in the form of `github.com/bpfman/bpfman` which includes the following:
+ - `github.com/bpfman/bpfman/clients/gobpfman/v1`: The go client for the bpfman GRPC API
+ - `github.com/bpfman/bpfman/bpfman-operator/apis`: The go bindings for the
+ bpfman CRD API
+ - `github.com/bpfman/bpfman/bpfman-operator/pkg/client`: The autogenerated
+ clientset for the bpfman CRD API
+ - `github.com/bpfman/bpfman/bpfman-operator/pkg/helpers`: The provided bpfman CRD
+ API helpers.
- The following core component container images with tag :
- - `quay.io/bpfman/bpfman`
- - `quay.io/bpfman/bpfman-operator`
- - `quay.io/bpfman/bpfman-agent`
- - `quay.io/bpfman/bpfman-operator-bundle`
- - `quay.io/bpfman/xdp-dispatcher`
- - `quay.io/bpfman/tc-dispatcher`
+ - `quay.io/bpfman/bpfman`
+ - `quay.io/bpfman/bpfman-operator`
+ - `quay.io/bpfman/bpfman-agent`
+ - `quay.io/bpfman/bpfman-operator-bundle`
+ - `quay.io/bpfman/xdp-dispatcher`
+ - `quay.io/bpfman/tc-dispatcher`
- The relevant example bytecode container images with tag from source
code located in the bpfman project:
- - `quay.io/bpfman-bytecode/go_xdp_counter`
- - `quay.io/bpfman-bytecode/go_tc_counter`
- - `quay.io/bpfman-bytecode/go_tracepoint_counter`
- - `quay.io/bpfman-bytecode/xdp_pass`
- - `quay.io/bpfman-bytecode/tc_pass`
- - `quay.io/bpfman-bytecode/tracepoint`
- - `quay.io/bpfman-bytecode/xdp_pass_private`
- - `quay.io/bpfman-bytecode/uprobe`
- - `quay.io/bpfman-bytecode/kprobe`
- - `quay.io/bpfman-bytecode/uretprobe`
- - `quay.io/bpfman-bytecode/kretprobe`
+ - `quay.io/bpfman-bytecode/go-xdp-counter`
+ - `quay.io/bpfman-userspace/go-target`
+ - `quay.io/bpfman-bytecode/go-tc-counter`
+ - `quay.io/bpfman-bytecode/go-tracepoint-counter`
+ - `quay.io/bpfman-bytecode/xdp-pass`
+ - `quay.io/bpfman-bytecode/tc-pass`
+ - `quay.io/bpfman-bytecode/tracepoint`
+ - `quay.io/bpfman-bytecode/xdp-pass-private`
+ - `quay.io/bpfman-bytecode/go-uprobe-counter`
+ - `quay.io/bpfman-bytecode/go-kprobe-counter`
+ - `quay.io/bpfman-bytecode/uprobe`
+ - `quay.io/bpfman-bytecode/kprobe`
+ - `quay.io/bpfman-bytecode/uretprobe`
+ - `quay.io/bpfman-bytecode/kretprobe`
+ - `quay.io/bpfman-bytecode/fentry`
+ - `quay.io/bpfman-bytecode/fexit`
- The relevant example userspace container images with tag from source
code located in the bpfman project:
- - `quay.io/bpfman-userspace/go_xdp_counter`
- - `quay.io/bpfman-userspace/go_tc_counter`
- - `quay.io/bpfman-userspace/go_tracepoint_counter`
+ - `quay.io/bpfman-userspace/go-xdp-counter`
+ - `quay.io/bpfman-userspace/go-tc-counter`
+ - `quay.io/bpfman-userspace/go-tracepoint-counter`
+ - `quay.io/bpfman-userspace/go-uprobe-counter`
+ - `quay.io/bpfman-userspace/go-kprobe-counter`
- The OLM (Operator Lifecycle Manager) for the Kubernetes Operator.
- - This includes a `bundle` directory on disk as well as the
- `quay.io/bpfman/bpfman-operator-bundle` with the tag .
+ - This includes a `bundle` directory on disk as well as the
+ `quay.io/bpfman/bpfman-operator-bundle` with the tag .
## Versioning strategy
@@ -94,21 +110,21 @@ For a **PATCH** release:
`git checkout -b release-x.x.x `
- Create a pull request of the `/release-x.x.x` branch into the `release-x.x` branch upstream.
Add a hold on this PR waiting for at least one maintainer/codeowner to provide a `lgtm`. This PR should:
- - Add a new changelog for the release
- - Update the cargo.toml versions for the bpfman-api and bpfman crates
- - Update the bpfman-operator version in it's MAKEFILE and run `make bundle` to update the bundle version.
- This will generate a new `/bpfman-operator/bundle` directory which will ONLY be tracked in the
- `release-x.x` branch not `main`.
+ - Add a new changelog for the release
+ - Update the cargo.toml version for the workspace.
+ - Update the bpfman-operator version in it's MAKEFILE and run `make bundle` to update the bundle version.
+ This will generate a new `/bpfman-operator/bundle` directory which will ONLY be tracked in the
+ `release-x.x` branch not `main`.
- Verify the CI tests pass and merge the PR into `release-x.x`.
- Create a tag using the `HEAD` of the `release-x.x.x` branch. This can be done using the `git` CLI or
Github's [release][release] page.
- The Release will be automatically created, after that is complete do the following:
- - run `make build-release-yamls` and attach the yamls for the version to the release. These will include:
- - `bpfman-crds-install-vx.x.x.yaml`
- - `bpfman-operator-install-vx.x.x.yaml`
- - `go-xdp-counter-install-vx.x.x.yaml`
- - `go-tc-counter-install-vx.x.x.yaml`
- - `go-tracepoint-counter-install-vx.x.x.yaml`
+ - run `make build-release-yamls` and attach the yamls for the version to the release. These will include:
+ - `bpfman-crds-install.yaml`
+ - `bpfman-operator-install.yaml`
+ - `go-xdp-counter-install.yaml`
+ - `go-tc-counter-install.yaml`
+ - `go-tracepoint-counter-install.yaml`
- Update the [community-operator](https://github.com/k8s-operatorhub/community-operators) and
[community-operators-prod](https://github.com/redhat-openshift-ecosystem/community-operators-prod) repositories with
the latest bundle manifests. See the following PRs as examples:
@@ -118,10 +134,10 @@ For a **PATCH** release:
For a **MAJOR** or **MINOR** release:
- Open an update PR that:
- - Adds a new changelog for the release
- - Updates the cargo.toml versions for the bpfman-api and bpfman crates
- - Updates the bpfman-operator version in it's MAKEFILE and run `make bundle` to update the bundle version
- - Add's a new `examples` config directory for the release version
+ - Adds a new changelog for the release
+ - Updates the cargo.toml version for the workspace.
+ - Updates the bpfman-operator version in it's MAKEFILE and run `make bundle` to update the bundle version
+ - Add's a new `examples` config directory for the release version
- Make sure CI is green and merge the update PR.
- Create a tag using the `HEAD` of the `main` branch. This can be done using the `git` CLI or
Github's [release][release] page.
@@ -129,12 +145,12 @@ For a **MAJOR** or **MINOR** release:
This can be done using the `git` CLI or Github's [release][release]
page.
- The Release will be automatically created, after that is complete do the following:
- - run `make build-release-yamls` and attach the yamls for the version to the release. These will include:
- - `bpfman-crds-install-vx.x.x.yaml`
- - `bpfman-operator-install-vx.x.x.yaml`
- - `go-xdp-counter-install-vx.x.x.yaml`
- - `go-tc-counter-install-vx.x.x.yaml`
- - `go-tracepoint-counter-install-vx.x.x.yaml`
+ - run `make build-release-yamls` and attach the yamls for the version to the release. These will include:
+ - `bpfman-crds-install.yaml`
+ - `bpfman-operator-install.yaml`
+ - `go-xdp-counter-install.yaml`
+ - `go-tc-counter-install.yaml`
+ - `go-tracepoint-counter-install.yaml`
[release]: https://github.com/bpfman/bpfman/releases
[bpfman-team]: https://github.com/bpfman/bpfman/blob/main/CODEOWNERS
diff --git a/REVIEWING.md b/REVIEWING.md
index 7a495a644..d296236e7 100644
--- a/REVIEWING.md
+++ b/REVIEWING.md
@@ -35,7 +35,7 @@ Be trustworthy. During a review, your actions both build and help maintain the t
## Process
-* Reviewers are automatically assigned based on the [CODEOWNERS](./CODEOWNERS) file.
+* Reviewers are automatically assigned based on the [CODEOWNERS](https://github.com/bpfman/bpfman/blob/main/CODEOWNERS) file.
* Reviewers should wait for automated checks to pass before reviewing
* At least 1 approved review is required from a maintainer before a pull request can be merged
* All CI checks must pass
diff --git a/bpf-log-exporter/Cargo.toml b/bpf-log-exporter/Cargo.toml
new file mode 100644
index 000000000..8e2b1aecb
--- /dev/null
+++ b/bpf-log-exporter/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+description = "Binary for exporting eBPF events via logs"
+documentation.workspace = true
+edition.workspace = true
+homepage.workspace = true
+license.workspace = true
+name = "bpf-log-exporter"
+repository.workspace = true
+version.workspace = true
+
+[dependencies]
+anyhow = { workspace = true }
+env_logger = { workspace = true }
+futures = { workspace = true }
+hex = { workspace = true, features = ["alloc"] }
+log = { workspace = true }
+netlink-packet-audit = { workspace = true }
+netlink-packet-core = { workspace = true }
+netlink-sys = { workspace = true }
+regex = { workspace = true }
diff --git a/bpf-log-exporter/src/main.rs b/bpf-log-exporter/src/main.rs
new file mode 100644
index 000000000..b88520975
--- /dev/null
+++ b/bpf-log-exporter/src/main.rs
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Authors of bpfman
+
+use std::{cell::RefCell, num::NonZeroI32, str::from_utf8};
+
+use anyhow::{anyhow, Context};
+use log::info;
+use netlink_packet_audit::AuditMessage;
+use netlink_packet_core::{NetlinkMessage, NetlinkPayload};
+use netlink_sys::{protocols::NETLINK_AUDIT, Socket, SocketAddr};
+use regex::Regex;
+
+fn main() -> anyhow::Result<()> {
+ env_logger::init();
+ let audit = NetlinkAudit::new();
+ audit
+ .recieve_loop()
+ .context("failed to receive audit events")?;
+ Ok(())
+}
+
+struct NetlinkAudit {
+ sock: RefCell,
+}
+
+#[derive(Debug)]
+struct LogMessage {
+ timestamp: String,
+ prog_id: u32,
+ op: String,
+ syscall_op: u32,
+ pid: u32,
+ uid: u32,
+ gid: u32,
+ comm: String,
+ cmdline: String,
+}
+
+impl LogMessage {
+ fn new() -> Self {
+ LogMessage {
+ timestamp: String::new(),
+ prog_id: 0,
+ op: String::new(),
+ syscall_op: 0,
+ pid: 0,
+ uid: 0,
+ gid: 0,
+ comm: String::new(),
+ cmdline: String::new(),
+ }
+ }
+}
+
+const NETLINK_GROUP_READ_LOG: u32 = 0x1;
+
+impl NetlinkAudit {
+ fn new() -> Self {
+ let mut sock = Socket::new(NETLINK_AUDIT).unwrap();
+ sock.bind_auto().unwrap();
+ sock.connect(&SocketAddr::new(0, 0)).unwrap();
+ sock.add_membership(NETLINK_GROUP_READ_LOG)
+ .expect("failed to add membership");
+ NetlinkAudit {
+ sock: RefCell::new(sock),
+ }
+ }
+
+ fn recieve_loop(&self) -> anyhow::Result<()> {
+ let mut receive_buffer = vec![0; 4096];
+ let socket = self.sock.borrow_mut();
+ let is_hex = Regex::new(r"^[0-9A-F]+$").unwrap();
+ let mut log_paylod = LogMessage::new();
+ loop {
+ let n = socket.recv(&mut &mut receive_buffer[..], 0)?;
+ let bytes = &receive_buffer[..n];
+ let rx_packet = >::deserialize(bytes).unwrap();
+ match rx_packet.payload {
+ NetlinkPayload::InnerMessage(AuditMessage::Event((event_id, message))) => {
+ match event_id {
+ 1300 => {
+ // syscall event - 321 is sys_bpf
+ if message.contains("syscall=321") {
+ let parts: Vec<&str> = message.split_whitespace().collect();
+ let a0 = parts
+ .iter()
+ .find(|s| s.starts_with("a0="))
+ .unwrap()
+ .split('=')
+ .last()
+ .unwrap();
+ let uid = parts
+ .iter()
+ .find(|s| s.starts_with("uid="))
+ .expect("uid not found")
+ .split('=')
+ .last()
+ .unwrap();
+ let pid = parts
+ .iter()
+ .find(|s| s.starts_with("pid="))
+ .expect("pid not found")
+ .split('=')
+ .last()
+ .unwrap();
+ let gid = parts
+ .iter()
+ .find(|s| s.starts_with("gid="))
+ .expect("gid not found")
+ .split('=')
+ .last()
+ .unwrap();
+ let comm = parts
+ .iter()
+ .find(|s| s.starts_with("comm="))
+ .unwrap()
+ .split('=')
+ .last()
+ .unwrap();
+
+ log_paylod.syscall_op = a0.parse().unwrap();
+ log_paylod.uid = uid.parse().unwrap();
+ log_paylod.pid = pid.parse().unwrap();
+ log_paylod.gid = gid.parse().unwrap();
+ log_paylod.comm = comm.replace('"', "").to_string();
+ }
+ }
+ 1334 => {
+ // ebpf event
+ let parts: Vec<&str> = message.split_whitespace().collect();
+ let prog_id = parts
+ .iter()
+ .find(|s| s.starts_with("prog-id="))
+ .unwrap()
+ .split('=')
+ .last()
+ .unwrap();
+ let op = parts
+ .iter()
+ .find(|s| s.starts_with("op="))
+ .unwrap()
+ .split('=')
+ .last()
+ .unwrap();
+ log_paylod.prog_id = prog_id.parse().unwrap();
+ log_paylod.op = op.to_string();
+ }
+ 1327 => {
+ // proctitle emit event
+ let parts = message.split_whitespace().collect::>();
+ let timestamp = parts
+ .iter()
+ .find(|s| s.starts_with("audit("))
+ .unwrap()
+ .split(':')
+ .next()
+ .unwrap();
+ log_paylod.timestamp = timestamp.replace("audit(", "").to_string();
+
+ let proctile = parts
+ .iter()
+ .find(|s| s.starts_with("proctitle="))
+ .unwrap()
+ .split('=')
+ .last()
+ .unwrap();
+ if is_hex.is_match(proctile) {
+ log_paylod.cmdline = from_utf8(&hex::decode(proctile).unwrap())
+ .unwrap()
+ .replace('\0', " ")
+ .to_string();
+ } else {
+ log_paylod.cmdline = proctile.replace('"', "").to_string();
+ }
+ }
+ 1320 => {
+ info!("{:?}", log_paylod);
+ log_paylod = LogMessage::new();
+ }
+ _ => {}
+ }
+ }
+ NetlinkPayload::InnerMessage(AuditMessage::Other(_)) => {
+ // discard messages that are not events
+ }
+ NetlinkPayload::Error(e) => {
+ if e.code == NonZeroI32::new(-17) {
+ break;
+ } else {
+ return Err(anyhow!(e));
+ }
+ }
+ m => return Err(anyhow!("unexpected netlink message {:?}", m)),
+ }
+ }
+ Ok(())
+ }
+}
diff --git a/bpf-metrics-exporter/Cargo.toml b/bpf-metrics-exporter/Cargo.toml
index e7a30dacf..d7c75f9df 100644
--- a/bpf-metrics-exporter/Cargo.toml
+++ b/bpf-metrics-exporter/Cargo.toml
@@ -1,11 +1,16 @@
[package]
-edition = "2021"
+description = "Binary for exporting eBPF subsystem metrics via prometheus"
+edition.workspace = true
+license.workspace = true
name = "bpf-metrics-exporter"
-version = "0.1.0"
+repository.workspace = true
+version.workspace = true
[dependencies]
anyhow = { workspace = true }
aya = { workspace = true }
+bpfman = { workspace = true }
+bpfman-api = { workspace = true }
chrono = { workspace = true, features = ["std"] }
clap = { workspace = true, features = [
"color",
diff --git a/bpf-metrics-exporter/README.md b/bpf-metrics-exporter/README.md
index 20dd8e393..86f998812 100644
--- a/bpf-metrics-exporter/README.md
+++ b/bpf-metrics-exporter/README.md
@@ -12,26 +12,77 @@ to correlate process IDs -> containers -> k8s pods.
## Metrics
-The following metrics are exported:
-
-- `bpf_program_size_jitted_bytes`: The size of the BPF program in bytes.
+The following metrics are currently exported, this list will continue to expand:
+
+### [Gauges](https://opentelemetry.io/docs/specs/otel/metrics/api/#gauge)
+
+- `bpf_program_info`: Information on each loaded BPF Program
+ - Labels:
+ - `id`: The ID of the BPF program
+ - `name`: The name of the BPF program
+ - `type`: The type of the BPF program as a readable string
+ - `tag`: The tag of the BPF program
+ - `gpl_compatible`: Whether the BPF program is GPL compatible
+ - `load_time`: The time the BPF program was loaded
+- `bpf_map_info`: Information of each loaded BPF Map
+ - Labels:
+ - `id`: The ID of the BPF map
+ - `name`: The name of the BPF map
+ - `type`: The type of the BPF map as an `u32` which corresponds to the following [kernel enumeration](https://elixir.bootlin.com/linux/v6.6.3/source/include/uapi/linux/bpf.h#L906)
+ - `key_size`: The key size in bytes for the BPF map
+ - `value_size`: The value size for the BPF map
+ - `max_entries`: The maximum number of entries for the BPF map.
+ - `flags`: Loadtime specific flags for the BPF map
+- `bpf_link_info`: Information on each of the loaded BPF Link
+ - Labels:
+ - `id`: The ID of the bpf Link
+ - `prog_id`: The Program ID of the BPF program which is using the Link.
+ - `type`: The BPF Link type as a `u32` which corresponds to the following [kernel enumeration](https://elixir.bootlin.com/linux/v6.6.3/source/include/uapi/linux/bpf.h#L1048)
+- `bpf_program_load_time`: The standard UTC time the program was loaded in seconds
+ - Labels:
+ - `id`: The ID of the BPF program
+ - `name`: The name of the BPF program
+ - `type`: The type of the BPF program as a readable string
+
+### [Counters](https://opentelemetry.io/docs/specs/otel/metrics/api/#counter)
+
+**Note**: All counters will have the [suffix `_total` appended](https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#counter-1).
+
+- `bpf_program_size_jitted_bytes`: The size in bytes of the program's JIT-compiled machine code.
+ - Labels:
+ - `id`: The ID of the BPF program
+ - `name`: The name of the BPF program
+ - `type`: The type of the BPF program as a readable string
- `bpf_program_size_translated_bytes`: The size of the BPF program in bytes.
+ - Labels:
+ - `id`: The ID of the BPF program
+ - `name`: The name of the BPF program
+ - `type`: The type of the BPF program as a readable string
- `bpf_program_mem_bytes`: The amount of memory used by the BPF program in bytes.
-- `bpf_program_verified_instructions_total`: The number of instructions in the BPF program.
-
-
-The following will be added pending: https://github.com/open-telemetry/opentelemetry-rust/issues/1242
-
-- `bpf_programs_total`: The number of BPF programs loaded into the kernel.
-
-Labels:
-
-- `id`: The ID of the BPF program
-- `name`: The name of the BPF program
-- `type`: The type of the BPF program
-- `tag`: The tag of the BPF program
-- `gpl_compatible`: Whether the BPF program is GPL compatible
-- `load_time`: The time the BPF program was loaded
+ - Labels:
+ - `id`: The ID of the BPF program
+ - `name`: The name of the BPF program
+ - `type`: The type of the BPF program as a readable string
+- `bpf_program_verified_instructions`: The number of instructions in the BPF program.
+ - Labels:
+ - `id`: The ID of the BPF program
+ - `name`: The name of the BPF program
+ - `type`: The type of the BPF program as a readable string
+- `bpf_map_key_size`: The size of the BPF map key
+ - Labels:
+ - `id`: The ID of the BPF map
+ - `name`: The name of the BPF map
+ - `type`: The type of the BPF map as an `u32` which corresponds to the following [kernel enumeration](https://elixir.bootlin.com/linux/v6.6.3/source/include/uapi/linux/bpf.h#L906)
+- `bpf_map_value_size`: The size of the BPF map value
+ - Labels:
+ - `id`: The ID of the BPF map
+ - `name`: The name of the BPF map
+ - `type`: The type of the BPF map as an `u32` which corresponds to the following [kernel enumeration](https://elixir.bootlin.com/linux/v6.6.3/source/include/uapi/linux/bpf.h#L906)
+- `bpf_map_max_entries`: The maximum number of entries allowed for the BPF map
+ - Labels:
+ - `id`: The ID of the BPF map
+ - `name`: The name of the BPF map
+ - `type`: The type of the BPF map as an `u32` which corresponds to the following [kernel enumeration](https://elixir.bootlin.com/linux/v6.6.3/source/include/uapi/linux/bpf.h#L906)
## Try it out
@@ -44,9 +95,20 @@ podman play kube metrics-stack.yaml
Then, you can deploy the exporter:
-```
+```bash
sudo ./target/debug/bpf-metrics-exporter
```
-You can log into grafana at http://localhost:3000/ with the credentials `admin:admin`. Once there, you can explore the metrics in the prometheus
-datasource.
+You can log into grafana at `http://localhost:3000/` using the default user:password
+`admin:admin`.
+
+From there simply select the default dashboard titled `eBPF Subsystem Metrics`:
+
+
+
+In order to clean everything up simply exit the bpf-metrics-exporter process with
+`ctrl+c` and run:
+
+```bash
+podman kube down metrics-stack.yaml
+```
diff --git a/bpf-metrics-exporter/bpf_traces.bt b/bpf-metrics-exporter/bpf_traces.bt
new file mode 100755
index 000000000..13406c231
--- /dev/null
+++ b/bpf-metrics-exporter/bpf_traces.bt
@@ -0,0 +1,37 @@
+#!/usr/bin/env bpftrace
+
+kfunc:vmlinux:bpf_prog_load {
+ printf("Process %d is loading a %d bpf program at %s\n", pid, args->attr->prog_type, strftime("%H:%M:%S", nsecs));
+}
+
+kfunc:vmlinux:bpf_prog_new_fd {
+ printf("Process %d is loading a %d bpf program with id %d at %s\n", pid, args->prog->aux->saved_dst_prog_type, args->prog->aux->id, strftime("%H:%M:%S", nsecs));
+}
+
+kretfunc:vmlinux:bpf_prog_load {
+ if (retval != 0){
+ printf("Process %d failed to load bpfprogram", pid)
+ } else {
+ printf("Process %d successfully loaded a bpfprogram\n", pid);
+ }
+}
+
+kfunc:vmlinux:bpf_prog_free {
+ printf("Program %d released\n", args->fp->aux->id)
+}
+
+kfunc:vmlinux:bpf_prog_free_id {
+ printf("Program %d unloaded", args->prog->aux->id)
+}
+
+kretfunc:vmlinux:bpf_prog_free_id {
+ printf("Program %d unloaded", args->prog->aux->id)
+}
+
+tracepoint:syscalls:sys_enter_bpf {
+ printf("Enter bpf with pid %d \n", pid)
+}
+
+tracepoint:syscalls:sys_exit_bpf {
+ printf("Exit bpf with pid %d and ret %d \n", pid, args->ret)
+}
\ No newline at end of file
diff --git a/bpf-metrics-exporter/dashboard-example.png b/bpf-metrics-exporter/dashboard-example.png
new file mode 100644
index 000000000..2d5418ae1
Binary files /dev/null and b/bpf-metrics-exporter/dashboard-example.png differ
diff --git a/bpf-metrics-exporter/metrics-stack.yaml b/bpf-metrics-exporter/metrics-stack.yaml
index 28509c0df..9d60b5a81 100644
--- a/bpf-metrics-exporter/metrics-stack.yaml
+++ b/bpf-metrics-exporter/metrics-stack.yaml
@@ -88,6 +88,984 @@ data:
receivers: [otlp]
exporters: [prometheusremotewrite]
---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: grafana-datasources
+ namespace: monitoring
+data:
+ prometheus.yaml: |-
+ {
+ "apiVersion": 1,
+ "datasources": [
+ {
+ "access":"proxy",
+ "editable": true,
+ "name": "prometheus",
+ "orgId": 1,
+ "type": "prometheus",
+ "url": "http://localhost:9009/prometheus",
+ "version": 1
+ }
+ ]
+ }
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: grafana-dashboards
+ namespace: monitoring
+data:
+ dashboard.yaml: |-
+ {
+ "apiVersion": 1,
+ "providers": [
+ {
+ "name":"eBPF Subsystem Metrics",
+ "type":"file",
+ "options": {
+ "path": "/etc/grafana/provisioning/dashboards/ebpf-subsystem-metrics.json"
+ }
+ }
+ ]
+ }
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: ebpf-subsystem-metrics-dashboard
+ namespace: monitoring
+data:
+ ebpf-subsystem-metrics.json: |-
+ {
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "grafana",
+ "uid": "-- Grafana --"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "type": "dashboard"
+ }
+ ]
+ },
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 0,
+ "links": [],
+ "liveNow": false,
+ "panels": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "P1809F7CD0C75ACF3"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "bpf_programs {__name__=\"bpf_programs\", job=\"bpf-metrics-exporter\"}"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Programs"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "{__name__=\"bpf_maps\", job=\"bpf-metrics-exporter\"}"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Maps"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "{__name__=\"bpf_links\", job=\"bpf-metrics-exporter\"}"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Links"
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 1,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "center",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "10.2.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "P1809F7CD0C75ACF3"
+ },
+ "disableTextWrap": false,
+ "editorMode": "builder",
+ "expr": "sum(bpf_program_info)",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "instant": false,
+ "legendFormat": "Programs",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "P1809F7CD0C75ACF3"
+ },
+ "disableTextWrap": false,
+ "editorMode": "builder",
+ "expr": "sum(bpf_map_info)",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": true,
+ "instant": false,
+ "legendFormat": "Maps",
+ "range": true,
+ "refId": "B",
+ "useBackend": false
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "P1809F7CD0C75ACF3"
+ },
+ "disableTextWrap": false,
+ "editorMode": "builder",
+ "expr": "sum(bpf_link_info)",
+ "fullMetaSearch": false,
+ "hide": false,
+ "includeNullMetadata": true,
+ "instant": false,
+ "legendFormat": "Links",
+ "range": true,
+ "refId": "C",
+ "useBackend": false
+ }
+ ],
+ "title": "Subsystem Entities",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "P1809F7CD0C75ACF3"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "fixedColor": "purple",
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "align": "auto",
+ "cellOptions": {
+ "mode": "gradient",
+ "type": "color-background"
+ },
+ "filterable": false,
+ "inspect": false
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 6,
+ "w": 24,
+ "x": 0,
+ "y": 8
+ },
+ "id": 2,
+ "options": {
+ "cellHeight": "sm",
+ "footer": {
+ "countRows": false,
+ "fields": "",
+ "reducer": [
+ "sum"
+ ],
+ "show": false
+ },
+ "frameIndex": 3,
+ "showHeader": true,
+ "sortBy": []
+ },
+ "pluginVersion": "10.2.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "P1809F7CD0C75ACF3"
+ },
+ "disableTextWrap": false,
+ "editorMode": "builder",
+ "exemplar": false,
+ "expr": "bpf_program_info",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "instant": true,
+ "legendFormat": "__auto",
+ "range": false,
+ "refId": "A",
+ "useBackend": false
+ }
+ ],
+ "title": "Loaded Program Info",
+ "transformations": [
+ {
+ "id": "reduce",
+ "options": {
+ "includeTimeField": true,
+ "mode": "reduceFields",
+ "reducers": [
+ "last"
+ ]
+ }
+ },
+ {
+ "id": "labelsToFields",
+ "options": {
+ "keepLabels": [
+ "gpl_compatible",
+ "job",
+ "load_time",
+ "tag",
+ "type",
+ "name",
+ "map_ids",
+ "id"
+ ],
+ "mode": "columns"
+ }
+ },
+ {
+ "id": "merge",
+ "options": {}
+ },
+ {
+ "id": "organize",
+ "options": {
+ "excludeByName": {
+ "Time": true,
+ "bpf_programs": true,
+ "job": true
+ },
+ "indexByName": {
+ "Time": 0,
+ "bpf_programs": 1,
+ "gpl_compatible": 9,
+ "id": 4,
+ "job": 6,
+ "load_time": 5,
+ "map_ids": 7,
+ "name": 2,
+ "tag": 8,
+ "type": 3
+ },
+ "renameByName": {}
+ }
+ },
+ {
+ "id": "reduce",
+ "options": {
+ "includeTimeField": false,
+ "mode": "reduceFields",
+ "reducers": []
+ }
+ }
+ ],
+ "transparent": true,
+ "type": "table"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "P1809F7CD0C75ACF3"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "fixedColor": "purple",
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "align": "auto",
+ "cellOptions": {
+ "mode": "gradient",
+ "type": "color-background"
+ },
+ "filterable": false,
+ "inspect": false
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 6,
+ "w": 24,
+ "x": 0,
+ "y": 14
+ },
+ "id": 6,
+ "options": {
+ "cellHeight": "sm",
+ "footer": {
+ "countRows": false,
+ "fields": "",
+ "reducer": [
+ "sum"
+ ],
+ "show": false
+ },
+ "frameIndex": 3,
+ "showHeader": true,
+ "sortBy": [
+ {
+ "desc": true,
+ "displayName": "load_time"
+ }
+ ]
+ },
+ "pluginVersion": "10.2.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "P1809F7CD0C75ACF3"
+ },
+ "disableTextWrap": false,
+ "editorMode": "builder",
+ "exemplar": false,
+ "expr": "bpf_map_info",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "instant": true,
+ "legendFormat": "__auto",
+ "range": false,
+ "refId": "A",
+ "useBackend": false
+ }
+ ],
+ "title": "Loaded Map Info",
+ "transformations": [
+ {
+ "id": "reduce",
+ "options": {
+ "includeTimeField": true,
+ "mode": "reduceFields",
+ "reducers": [
+ "last"
+ ]
+ }
+ },
+ {
+ "id": "labelsToFields",
+ "options": {
+ "keepLabels": [
+ "flags",
+ "id",
+ "key_size",
+ "max_entries",
+ "name",
+ "type",
+ "value_size"
+ ],
+ "mode": "columns"
+ }
+ },
+ {
+ "id": "merge",
+ "options": {}
+ },
+ {
+ "id": "organize",
+ "options": {
+ "excludeByName": {
+ "Time": true,
+ "bpf_maps": true,
+ "bpf_programs": true,
+ "job": true
+ },
+ "indexByName": {
+ "Time": 0,
+ "bpf_maps": 4,
+ "flags": 8,
+ "id": 1,
+ "key_size": 5,
+ "max_entries": 7,
+ "name": 2,
+ "type": 3,
+ "value_size": 6
+ },
+ "renameByName": {}
+ }
+ },
+ {
+ "id": "reduce",
+ "options": {
+ "includeTimeField": false,
+ "mode": "reduceFields",
+ "reducers": []
+ }
+ }
+ ],
+ "transparent": true,
+ "type": "table"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "P1809F7CD0C75ACF3"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "fixedColor": "purple",
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "align": "auto",
+ "cellOptions": {
+ "mode": "gradient",
+ "type": "color-background"
+ },
+ "filterable": false,
+ "inspect": false
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 24,
+ "x": 0,
+ "y": 20
+ },
+ "id": 7,
+ "options": {
+ "cellHeight": "sm",
+ "footer": {
+ "countRows": false,
+ "fields": "",
+ "reducer": [
+ "sum"
+ ],
+ "show": false
+ },
+ "frameIndex": 3,
+ "showHeader": true,
+ "sortBy": [
+ {
+ "desc": true,
+ "displayName": "load_time"
+ }
+ ]
+ },
+ "pluginVersion": "10.2.2",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "P1809F7CD0C75ACF3"
+ },
+ "disableTextWrap": false,
+ "editorMode": "builder",
+ "exemplar": false,
+ "expr": "bpf_link_info",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "instant": true,
+ "legendFormat": "__auto",
+ "range": false,
+ "refId": "A",
+ "useBackend": false
+ }
+ ],
+ "title": "Loaded Link Info",
+ "transformations": [
+ {
+ "id": "reduce",
+ "options": {
+ "includeTimeField": true,
+ "mode": "reduceFields",
+ "reducers": [
+ "last"
+ ]
+ }
+ },
+ {
+ "id": "labelsToFields",
+ "options": {
+ "keepLabels": [
+ "flags",
+ "id",
+ "key_size",
+ "max_entries",
+ "name",
+ "type",
+ "value_size",
+ "prog_id"
+ ],
+ "mode": "columns"
+ }
+ },
+ {
+ "id": "merge",
+ "options": {}
+ },
+ {
+ "id": "organize",
+ "options": {
+ "excludeByName": {
+ "Time": true,
+ "bpf_links": true,
+ "bpf_maps": true,
+ "bpf_programs": true,
+ "job": true
+ },
+ "indexByName": {
+ "Time": 0,
+ "bpf_links": 4,
+ "id": 2,
+ "prog_id": 1,
+ "type": 3
+ },
+ "renameByName": {}
+ }
+ },
+ {
+ "id": "reduce",
+ "options": {
+ "includeTimeField": false,
+ "mode": "reduceFields",
+ "reducers": []
+ }
+ }
+ ],
+ "transparent": true,
+ "type": "table"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "P1809F7CD0C75ACF3"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "bytes"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 8,
+ "x": 0,
+ "y": 25
+ },
+ "id": 4,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": false
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "P1809F7CD0C75ACF3"
+ },
+ "disableTextWrap": false,
+ "editorMode": "builder",
+ "expr": "sum by() (bpf_program_verified_instructions_total)",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "instant": false,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ }
+ ],
+ "title": "Verified Instructions Total",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "P1809F7CD0C75ACF3"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "bytes"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 8,
+ "x": 8,
+ "y": 25
+ },
+ "id": 5,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": false
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "P1809F7CD0C75ACF3"
+ },
+ "disableTextWrap": false,
+ "editorMode": "builder",
+ "expr": "sum(bpf_program_size_translated_bytes_total)",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "instant": false,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ }
+ ],
+ "title": "Translated Bytes Total",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "P1809F7CD0C75ACF3"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 4,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "bytes"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 8,
+ "x": 16,
+ "y": 25
+ },
+ "id": 3,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": false
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "P1809F7CD0C75ACF3"
+ },
+ "disableTextWrap": false,
+ "editorMode": "builder",
+ "expr": "sum(bpf_program_mem_bytes_total)",
+ "fullMetaSearch": false,
+ "includeNullMetadata": false,
+ "instant": false,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ }
+ ],
+ "title": "Total Memory Locked by Bpf Programs",
+ "type": "timeseries"
+ }
+ ],
+ "refresh": "",
+ "schemaVersion": 38,
+ "tags": [],
+ "templating": {
+ "list": []
+ },
+ "time": {
+ "from": "now-15m",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "",
+ "title": "eBPF Subsystem Metrics",
+ "uid": "ce0664f5-8526-430a-aa32-832686caa496",
+ "version": 1,
+ "weekStart": ""
+ }
+---
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -127,6 +1105,11 @@ spec:
- mountPath: /etc/grafana/provisioning/datasources
name: grafana-datasources
readOnly: true
+ - mountPath: /etc/grafana/provisioning/dashboards
+ name: grafana-dashboards
+ - mountPath: /etc/grafana/provisioning/dashboards/ebpf-subsystem-metrics.json
+ name: ebpf-subsystem-metrics-dashboard
+ subPath: ebpf-subsystem-metrics.json
- name: mimir
image: docker.io/grafana/mimir:latest
args:
@@ -150,6 +1133,12 @@ spec:
- name: grafana-datasources
configMap:
name: grafana-datasources
+ - name: grafana-dashboards
+ configMap:
+ name: grafana-dashboards
+ - name: ebpf-subsystem-metrics-dashboard
+ configMap:
+ name: ebpf-subsystem-metrics-dashboard
- name: mimir-data
emptyDir: {}
- name: mimir-config
diff --git a/bpf-metrics-exporter/src/main.rs b/bpf-metrics-exporter/src/main.rs
index 501844526..46262b6ea 100644
--- a/bpf-metrics-exporter/src/main.rs
+++ b/bpf-metrics-exporter/src/main.rs
@@ -1,4 +1,8 @@
-use aya::loaded_programs;
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Authors of bpfman
+
+use aya::{loaded_programs, maps::loaded_maps, programs::loaded_links};
+use bpfman::types::ProgramType;
use chrono::{prelude::DateTime, Utc};
use clap::Parser;
use opentelemetry::{
@@ -6,7 +10,7 @@ use opentelemetry::{
KeyValue,
};
use opentelemetry_otlp::WithExportConfig;
-use opentelemetry_sdk::{metrics::MeterProvider as SdkMeterProvider, runtime, Resource};
+use opentelemetry_sdk::{metrics::SdkMeterProvider, runtime, Resource};
use tokio::signal::ctrl_c;
fn init_meter_provider(grpc_endpoint: &str) -> SdkMeterProvider {
@@ -42,82 +46,207 @@ async fn main() -> anyhow::Result<()> {
// Create a meter from the above MeterProvider.
let meter = meter_provider.meter("bpf-metrics");
- let bpf_program_size_jitted_bytes = meter
- .u64_observable_counter("bpf_program_size_jitted_bytes")
- .with_description("BPF program size in bytes")
- .with_unit(Unit::new("bytes"))
+ // GAUGE instruments:
+
+ let bpf_program_info = meter
+ .u64_observable_gauge("bpf_program_info")
+ .with_description("eBPF Program metadata")
+ .init();
+
+ let bpf_map_info = meter
+ .u64_observable_gauge("bpf_map_info")
+ .with_description("eBPF Map metadata")
+ .init();
+
+ let bpf_link_info = meter
+ .u64_observable_gauge("bpf_link_info")
+ .with_description("eBPF Link metadata")
.init();
+ let bpf_program_load_time = meter
+ .i64_observable_gauge("bpf_program_load_time")
+ .with_description("BPF program load time")
+ .with_unit(Unit::new("seconds"))
+ .init();
+
+ // COUNTER instruments:
+
let bpf_program_size_translated_bytes = meter
.u64_observable_counter("bpf_program_size_translated_bytes")
.with_description("BPF program size in bytes")
.with_unit(Unit::new("bytes"))
.init();
+ let bpf_program_size_jitted_bytes = meter
+ .u64_observable_counter("bpf_program_size_jitted_bytes")
+ .with_description("The size in bytes of the program's JIT-compiled machine code.")
+ .with_unit(Unit::new("bytes"))
+ .init();
+
let bpf_program_mem_bytes = meter
.u64_observable_counter("bpf_program_mem_bytes")
.with_description("BPF program memory usage in bytes")
.with_unit(Unit::new("bytes"))
.init();
- let bpf_program_verified_instructions_total = meter
- .u64_observable_counter("bpf_program_verified_instructions_total")
+ let bpf_program_verified_instructions = meter
+ .u64_observable_counter("bpf_program_verified_instructions")
.with_description("BPF program verified instructions")
.with_unit(Unit::new("instructions"))
.init();
+ let bpf_map_key_size = meter
+ .u64_observable_counter("bpf_map_key_size")
+ .with_description("BPF map key size")
+ .with_unit(Unit::new("bytes"))
+ .init();
+
+ let bpf_map_value_size = meter
+ .u64_observable_counter("bpf_map_value_size")
+ .with_description("BPF map value size")
+ .with_unit(Unit::new("bytes"))
+ .init();
+
+ let bpf_map_max_entries = meter
+ .u64_observable_counter("bpf_map_max_entries")
+ .with_description("BPF map maxiumum number of entries")
+ .with_unit(Unit::new("bytes"))
+ .init();
+
meter
.register_callback(
&[
+ bpf_program_info.as_any(),
+ bpf_map_info.as_any(),
+ bpf_link_info.as_any(),
+ bpf_program_load_time.as_any(),
bpf_program_size_jitted_bytes.as_any(),
bpf_program_size_translated_bytes.as_any(),
bpf_program_mem_bytes.as_any(),
- bpf_program_verified_instructions_total.as_any(),
+ bpf_program_verified_instructions.as_any(),
+ bpf_map_key_size.as_any(),
+ bpf_map_value_size.as_any(),
+ bpf_map_max_entries.as_any(),
],
move |observer| {
for program in loaded_programs().flatten() {
+ let id = program.id();
let name = program.name_as_str().unwrap_or_default().to_string();
- let ty = program.program_type().to_string();
+ let ty: ProgramType = program.program_type().try_into().unwrap();
let tag = program.tag().to_string();
let gpl_compatible = program.gpl_compatible();
- let load_time = DateTime::::from(program.loaded_at());
+ let map_ids = program.map_ids().unwrap_or_default();
+ let load_time = DateTime::::from(program.loaded_at());
let jitted_bytes = program.size_jitted();
let translated_bytes = program.size_translated();
let mem_bytes = program.memory_locked().unwrap_or_default();
let verified_instructions = program.verified_instruction_count();
- let labels = [
- KeyValue::new("name", name),
- KeyValue::new("type", ty),
- KeyValue::new("tag", tag),
+ let prog_info_labels = [
+ KeyValue::new("id", id.to_string()),
+ KeyValue::new("name", name.clone()),
+ KeyValue::new("type", format!("{ty}")),
+ KeyValue::new("tag", tag.clone()),
KeyValue::new("gpl_compatible", gpl_compatible),
+ KeyValue::new("map_ids", format!("{map_ids:?}")),
KeyValue::new(
"load_time",
load_time.format("%Y-%m-%d %H:%M:%S").to_string(),
),
];
+ observer.observe_u64(&bpf_program_info, 1, &prog_info_labels);
+
+ let prog_key_labels = [
+ KeyValue::new("id", id.to_string()),
+ KeyValue::new("name", name),
+ KeyValue::new("type", format!("{ty}")),
+ ];
+
+ observer.observe_i64(
+ &bpf_program_load_time,
+ load_time.timestamp(),
+ &prog_key_labels,
+ );
+
observer.observe_u64(
&bpf_program_size_jitted_bytes,
jitted_bytes.into(),
- &labels,
+ &prog_key_labels,
);
observer.observe_u64(
&bpf_program_size_translated_bytes,
translated_bytes.into(),
- &labels,
+ &prog_key_labels,
);
- observer.observe_u64(&bpf_program_mem_bytes, mem_bytes.into(), &labels);
+ observer.observe_u64(
+ &bpf_program_mem_bytes,
+ mem_bytes.into(),
+ &prog_key_labels,
+ );
observer.observe_u64(
- &bpf_program_verified_instructions_total,
+ &bpf_program_verified_instructions,
verified_instructions.into(),
- &labels,
+ &prog_key_labels,
);
}
+
+ for link in loaded_links().flatten() {
+ let id = link.id;
+ let prog_id = link.prog_id;
+ let _type = link.type_;
+ // TODO getting more link metadata will require an aya_patch
+ // let link_info = match link.__bindgen_anon_1 {
+ // // aya_obj::bpf_link_info__bindgen_ty_1::raw_tracepoint(i) => "tracepoint",
+ // aya_obj::generated::bpf_link_info__bindgen_ty_1{ raw_tracepoint } => format!("tracepoint name: "),
+ // }
+
+ let link_labels = [
+ KeyValue::new("id", id.to_string()),
+ KeyValue::new("prog_id", prog_id.to_string()),
+ KeyValue::new("type", _type.to_string()),
+ ];
+
+ observer.observe_u64(&bpf_link_info, 1, &link_labels);
+ }
+
+ for map in loaded_maps().flatten() {
+ let map_id = map.id();
+ let name = map.name_as_str().unwrap_or_default().to_string();
+ let ty = map.map_type().to_string();
+ let key_size = map.key_size();
+ let value_size = map.value_size();
+ let max_entries = map.max_entries();
+ let flags = map.map_flags();
+
+ let map_labels = [
+ KeyValue::new("name", name.clone()),
+ KeyValue::new("id", map_id.to_string()),
+ KeyValue::new("type", ty.clone()),
+ KeyValue::new("key_size", key_size.to_string()),
+ KeyValue::new("value_size", value_size.to_string()),
+ KeyValue::new("max_entries", max_entries.to_string()),
+ KeyValue::new("flags", flags.to_string()),
+ ];
+
+ observer.observe_u64(&bpf_map_info, 1, &map_labels);
+
+ let map_key_labels = [
+ KeyValue::new("name", name),
+ KeyValue::new("id", map_id.to_string()),
+ KeyValue::new("type", ty),
+ ];
+
+ observer.observe_u64(&bpf_map_key_size, key_size.into(), &map_key_labels);
+
+ observer.observe_u64(&bpf_map_value_size, value_size.into(), &map_key_labels);
+
+ observer.observe_u64(&bpf_map_max_entries, max_entries.into(), &map_key_labels);
+ }
},
)
.expect("failed to register callback");
diff --git a/bpfman-api/Cargo.toml b/bpfman-api/Cargo.toml
index 3502e9fe8..c958902c4 100644
--- a/bpfman-api/Cargo.toml
+++ b/bpfman-api/Cargo.toml
@@ -1,19 +1,72 @@
[package]
description = "gRPC bindings to the bpfman API"
-edition = "2021"
-license = "Apache-2.0"
+edition.workspace = true
+license.workspace = true
name = "bpfman-api"
-repository = "https://github.com/bpfman/bpfman"
-version = "0.3.1"
+repository.workspace = true
+version.workspace = true
+
+[[bin]]
+name = "bpfman-rpc"
+path = "src/bin/rpc/main.rs"
[dependencies]
+anyhow = { workspace = true, features = ["std"] }
+async-trait = { workspace = true }
aya = { workspace = true }
-clap = { workspace = true, features = ["derive", "std"] }
+base16ct = { workspace = true, features = ["alloc"] }
+base64 = { workspace = true }
+bpfman = { workspace = true }
+bpfman-csi = { workspace = true }
+caps = { workspace = true }
+chrono = { workspace = true }
+clap = { workspace = true, features = [
+ "color",
+ "derive",
+ "help",
+ "std",
+ "suggestions",
+ "usage",
+] }
+env_logger = { workspace = true }
+flate2 = { workspace = true, features = ["zlib"] }
+futures = { workspace = true }
+hex = { workspace = true, features = ["std"] }
+lazy_static = { workspace = true }
+libsystemd = { workspace = true }
log = { workspace = true }
+netlink-packet-route = { workspace = true }
+nix = { workspace = true, features = [
+ "fs",
+ "mount",
+ "net",
+ "resource",
+ "socket",
+ "user",
+] }
+oci-distribution = { workspace = true, default-features = false, features = [
+ "rustls-tls",
+ "trust-dns",
+] }
prost = { workspace = true, features = ["prost-derive", "std"] }
+rand = { workspace = true }
+rtnetlink = { workspace = true, features = ["tokio_socket"] }
serde = { workspace = true, features = ["derive"] }
+serde_json = { workspace = true, features = ["std"] }
+sha2 = { workspace = true }
+sigstore = { workspace = true, features = [
+ "cached-client",
+ "cosign-rustls-tls",
+ "sigstore-trust-root",
+] }
+sled = { workspace = true }
+systemd-journal-logger = { workspace = true }
+tar = { workspace = true }
+tempfile = { workspace = true }
thiserror = { workspace = true }
-tokio = { workspace = true, features = ["full"] }
+tokio = { workspace = true, features = ["full", "signal"] }
+tokio-stream = { workspace = true, features = ["net"] }
toml = { workspace = true, features = ["parse"] }
-tonic = { workspace = true, features = ["codegen", "prost"] }
+tonic = { workspace = true, features = ["codegen", "prost", "transport"] }
+tower = { workspace = true }
url = { workspace = true }
diff --git a/bpfman-api/src/bin/rpc/main.rs b/bpfman-api/src/bin/rpc/main.rs
new file mode 100644
index 000000000..392e96d9d
--- /dev/null
+++ b/bpfman-api/src/bin/rpc/main.rs
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Authors of bpfman
+use std::path::PathBuf;
+
+use clap::{Args, Parser};
+
+use crate::serve::serve;
+
+mod rpc;
+mod serve;
+mod storage;
+
+#[derive(Parser, Debug)]
+#[command(long_about = "A rpc server proxy for the bpfman library")]
+#[command(name = "bpfman-rpc")]
+pub(crate) struct Rpc {
+ /// Optional: Enable CSI support. Only supported when run in a Kubernetes
+ /// environment with bpfman-agent.
+ #[clap(long, verbatim_doc_comment)]
+ pub(crate) csi_support: bool,
+ /// Optional: Shutdown after N seconds of inactivity. Use 0 to disable.
+ #[clap(long, verbatim_doc_comment, default_value = "15")]
+ pub(crate) timeout: u64,
+ #[clap(long, default_value = "/run/bpfman-sock/bpfman.sock")]
+ /// Optional: Configure the location of the bpfman unix socket.
+ pub(crate) socket_path: PathBuf,
+}
+
+#[derive(Args, Debug)]
+#[command(disable_version_flag = true)]
+pub(crate) struct ServiceArgs {
+ /// Optional: Shutdown after N seconds of inactivity. Use 0 to disable.
+ #[clap(long, verbatim_doc_comment, default_value = "15")]
+ pub(crate) timeout: u64,
+ #[clap(long, default_value = "/run/bpfman-sock/bpfman.sock")]
+ /// Optional: Configure the location of the bpfman unix socket.
+ pub(crate) socket_path: PathBuf,
+}
+
+#[tokio::main]
+async fn main() -> anyhow::Result<()> {
+ let args = Rpc::parse();
+ //TODO https://github.com/bpfman/bpfman/issues/881
+ serve(args.csi_support, args.timeout, &args.socket_path).await?;
+
+ Ok(())
+}
diff --git a/bpfman-api/src/bin/rpc/rpc.rs b/bpfman-api/src/bin/rpc/rpc.rs
new file mode 100644
index 000000000..69d0addcb
--- /dev/null
+++ b/bpfman-api/src/bin/rpc/rpc.rs
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Authors of bpfman
+use bpfman::{
+ add_program, get_program, list_programs, pull_bytecode, remove_program,
+ types::{
+ FentryProgram, FexitProgram, KprobeProgram, ListFilter, Location, Program, ProgramData,
+ TcProceedOn, TcProgram, TracepointProgram, UprobeProgram, XdpProceedOn, XdpProgram,
+ },
+};
+use bpfman_api::v1::{
+ attach_info::Info, bpfman_server::Bpfman, bytecode_location::Location as RpcLocation,
+ list_response::ListResult, FentryAttachInfo, FexitAttachInfo, GetRequest, GetResponse,
+ KprobeAttachInfo, ListRequest, ListResponse, LoadRequest, LoadResponse, PullBytecodeRequest,
+ PullBytecodeResponse, TcAttachInfo, TracepointAttachInfo, UnloadRequest, UnloadResponse,
+ UprobeAttachInfo, XdpAttachInfo,
+};
+use tonic::{Request, Response, Status};
+
+pub struct BpfmanLoader {}
+
+impl BpfmanLoader {
+ pub(crate) fn new() -> BpfmanLoader {
+ BpfmanLoader {}
+ }
+}
+
+#[tonic::async_trait]
+impl Bpfman for BpfmanLoader {
+ async fn load(&self, request: Request) -> Result, Status> {
+ let request = request.into_inner();
+
+ let bytecode_source = match request
+ .bytecode
+ .ok_or(Status::aborted("missing bytecode info"))?
+ .location
+ .ok_or(Status::aborted("missing location"))?
+ {
+ RpcLocation::Image(i) => Location::Image(i.into()),
+ RpcLocation::File(p) => Location::File(p),
+ };
+
+ let data = ProgramData::new(
+ bytecode_source,
+ request.name,
+ request.metadata,
+ request.global_data,
+ request.map_owner_id,
+ )
+ .map_err(|e| Status::aborted(format!("failed to create ProgramData: {e}")))?;
+
+ let program = match request
+ .attach
+ .ok_or(Status::aborted("missing attach info"))?
+ .info
+ .ok_or(Status::aborted("missing info"))?
+ {
+ Info::XdpAttachInfo(XdpAttachInfo {
+ priority,
+ iface,
+ position: _,
+ proceed_on,
+ }) => Program::Xdp(
+ XdpProgram::new(
+ data,
+ priority,
+ iface,
+ XdpProceedOn::from_int32s(proceed_on)
+ .map_err(|_| Status::aborted("failed to parse proceed_on"))?,
+ )
+ .map_err(|e| Status::aborted(format!("failed to create xdpprogram: {e}")))?,
+ ),
+ Info::TcAttachInfo(TcAttachInfo {
+ priority,
+ iface,
+ position: _,
+ direction,
+ proceed_on,
+ }) => {
+ let direction = direction
+ .try_into()
+ .map_err(|_| Status::aborted("direction is not a string"))?;
+ Program::Tc(
+ TcProgram::new(
+ data,
+ priority,
+ iface,
+ TcProceedOn::from_int32s(proceed_on)
+ .map_err(|_| Status::aborted("failed to parse proceed_on"))?,
+ direction,
+ )
+ .map_err(|e| Status::aborted(format!("failed to create tcprogram: {e}")))?,
+ )
+ }
+ Info::TracepointAttachInfo(TracepointAttachInfo { tracepoint }) => Program::Tracepoint(
+ TracepointProgram::new(data, tracepoint)
+ .map_err(|e| Status::aborted(format!("failed to create tcprogram: {e}")))?,
+ ),
+ Info::KprobeAttachInfo(KprobeAttachInfo {
+ fn_name,
+ offset,
+ retprobe,
+ container_pid,
+ }) => Program::Kprobe(
+ KprobeProgram::new(data, fn_name, offset, retprobe, container_pid)
+ .map_err(|e| Status::aborted(format!("failed to create kprobeprogram: {e}")))?,
+ ),
+ Info::UprobeAttachInfo(UprobeAttachInfo {
+ fn_name,
+ offset,
+ target,
+ retprobe,
+ pid,
+ container_pid,
+ }) => Program::Uprobe(
+ UprobeProgram::new(data, fn_name, offset, target, retprobe, pid, container_pid)
+ .map_err(|e| Status::aborted(format!("failed to create uprobeprogram: {e}")))?,
+ ),
+ Info::FentryAttachInfo(FentryAttachInfo { fn_name }) => Program::Fentry(
+ FentryProgram::new(data, fn_name)
+ .map_err(|e| Status::aborted(format!("failed to create fentryprogram: {e}")))?,
+ ),
+ Info::FexitAttachInfo(FexitAttachInfo { fn_name }) => Program::Fexit(
+ FexitProgram::new(data, fn_name)
+ .map_err(|e| Status::aborted(format!("failed to create fexitprogram: {e}")))?,
+ ),
+ };
+
+ let program = add_program(program)
+ .await
+ .map_err(|e| Status::aborted(format!("{e}")))?;
+
+ let reply_entry =
+ LoadResponse {
+ info: Some((&program).try_into().map_err(|e| {
+ Status::aborted(format!("convert Program to GRPC program: {e}"))
+ })?),
+ kernel_info: Some((&program).try_into().map_err(|e| {
+ Status::aborted(format!("convert Program to GRPC kernel program info: {e}"))
+ })?),
+ };
+
+ Ok(Response::new(reply_entry))
+ }
+
+ async fn unload(
+ &self,
+ request: Request,
+ ) -> Result, Status> {
+ let reply = UnloadResponse {};
+ let request = request.into_inner();
+
+ remove_program(request.id)
+ .await
+ .map_err(|e| Status::aborted(format!("{e}")))?;
+
+ Ok(Response::new(reply))
+ }
+
+ async fn get(&self, request: Request) -> Result, Status> {
+ let request = request.into_inner();
+ let id = request.id;
+
+ let program = get_program(id)
+ .await
+ .map_err(|e| Status::aborted(format!("{e}")))?;
+
+ let reply_entry =
+ GetResponse {
+ info: if let Program::Unsupported(_) = program {
+ None
+ } else {
+ Some((&program).try_into().map_err(|e| {
+ Status::aborted(format!("failed to get program metadata: {e}"))
+ })?)
+ },
+ kernel_info: Some((&program).try_into().map_err(|e| {
+ Status::aborted(format!("convert Program to GRPC kernel program info: {e}"))
+ })?),
+ };
+ Ok(Response::new(reply_entry))
+ }
+
+ async fn list(&self, request: Request) -> Result, Status> {
+ let mut reply = ListResponse { results: vec![] };
+
+ let filter = ListFilter::new(
+ request.get_ref().program_type,
+ request.get_ref().match_metadata.clone(),
+ request.get_ref().bpfman_programs_only(),
+ );
+
+ // Await the response
+ for r in list_programs(filter)
+ .await
+ .map_err(|e| Status::aborted(format!("failed to list programs: {e}")))?
+ {
+ // Populate the response with the Program Info and the Kernel Info.
+ let reply_entry = ListResult {
+ info: if let Program::Unsupported(_) = r {
+ None
+ } else {
+ Some((&r).try_into().map_err(|e| {
+ Status::aborted(format!("failed to get program metadata: {e}"))
+ })?)
+ },
+ kernel_info: Some((&r).try_into().map_err(|e| {
+ Status::aborted(format!("convert Program to GRPC kernel program info: {e}"))
+ })?),
+ };
+ reply.results.push(reply_entry)
+ }
+ Ok(Response::new(reply))
+ }
+
+ async fn pull_bytecode(
+ &self,
+ request: tonic::Request,
+ ) -> std::result::Result, tonic::Status> {
+ let request = request.into_inner();
+ let image = match request.image {
+ Some(i) => i.into(),
+ None => return Err(Status::aborted("Empty pull_bytecode request received")),
+ };
+
+ pull_bytecode(image)
+ .await
+ .map_err(|e| Status::aborted(format!("{e}")))?;
+
+ let reply = PullBytecodeResponse {};
+ Ok(Response::new(reply))
+ }
+}
diff --git a/bpfman-api/src/bin/rpc/serve.rs b/bpfman-api/src/bin/rpc/serve.rs
new file mode 100644
index 000000000..dfd9c7c5d
--- /dev/null
+++ b/bpfman-api/src/bin/rpc/serve.rs
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Authors of bpfman
+
+use std::{
+ fs::{create_dir_all, remove_file},
+ os::unix::prelude::{FromRawFd, IntoRawFd},
+ path::Path,
+};
+
+use anyhow::{anyhow, Context};
+use bpfman::utils::{set_file_permissions, SOCK_MODE};
+use bpfman_api::v1::bpfman_server::BpfmanServer;
+use libsystemd::activation::IsType;
+use log::{debug, error, info};
+use tokio::{
+ join,
+ net::UnixListener,
+ signal::unix::{signal, SignalKind},
+ sync::broadcast,
+ task::{JoinHandle, JoinSet},
+};
+use tokio_stream::wrappers::UnixListenerStream;
+use tonic::transport::Server;
+
+use crate::{rpc::BpfmanLoader, storage::StorageManager};
+
+pub(crate) const RTDIR_SOCK: &str = "/run/bpfman-sock";
+// The CSI socket must be in it's own sub directory so we can easily create a dedicated
+// K8s volume mount for it.
+pub(crate) const RTDIR_BPFMAN_CSI: &str = "/run/bpfman/csi";
+
+pub async fn serve(csi_support: bool, timeout: u64, socket_path: &Path) -> anyhow::Result<()> {
+ let (shutdown_tx, shutdown_rx1) = broadcast::channel(32);
+ let shutdown_rx3 = shutdown_tx.subscribe();
+ let shutdown_handle = tokio::spawn(shutdown_handler(timeout, shutdown_tx));
+
+ let loader = BpfmanLoader::new();
+ let service = BpfmanServer::new(loader);
+
+ let mut listeners: Vec<_> = Vec::new();
+
+ let handle = serve_unix(socket_path, service.clone(), shutdown_rx1).await?;
+ listeners.push(handle);
+
+ if csi_support {
+ create_dir_all(RTDIR_BPFMAN_CSI).context("unable to create CSI directory")?;
+ create_dir_all(RTDIR_SOCK).context("unable to create socket directory")?;
+ let storage_manager = StorageManager::new();
+ let storage_manager_handle =
+ tokio::spawn(async move { storage_manager.run(shutdown_rx3).await });
+ let (_, res_storage, _) = join!(
+ join_listeners(listeners),
+ storage_manager_handle,
+ shutdown_handle
+ );
+ if let Some(e) = res_storage.err() {
+ return Err(e.into());
+ }
+ } else {
+ let (_, res) = join!(join_listeners(listeners), shutdown_handle);
+ if let Some(e) = res.err() {
+ return Err(e.into());
+ }
+ }
+
+ Ok(())
+}
+
+pub(crate) async fn shutdown_handler(timeout: u64, shutdown_tx: broadcast::Sender<()>) {
+ let mut joinset = JoinSet::new();
+ if timeout > 0 {
+ info!("Using inactivity timer of {} seconds", timeout);
+ let duration: std::time::Duration = std::time::Duration::from_secs(timeout);
+ joinset.spawn(Box::pin(tokio::time::sleep(duration)));
+ } else {
+ info!("Using no inactivity timer");
+ }
+ let mut sigint = signal(SignalKind::interrupt()).unwrap();
+ joinset.spawn(async move {
+ sigint.recv().await;
+ debug!("Received SIGINT");
+ });
+
+ let mut sigterm = signal(SignalKind::terminate()).unwrap();
+ joinset.spawn(async move {
+ sigterm.recv().await;
+ debug!("Received SIGTERM");
+ });
+
+ joinset.join_next().await;
+ shutdown_tx.send(()).unwrap();
+}
+
+async fn join_listeners(listeners: Vec>) {
+ for listener in listeners {
+ match listener.await {
+ Ok(()) => {}
+ Err(e) => eprintln!("Error = {e:?}"),
+ }
+ }
+}
+
+async fn serve_unix(
+ path: &Path,
+ service: BpfmanServer,
+ mut shutdown_channel: broadcast::Receiver<()>,
+) -> anyhow::Result> {
+ let uds_stream = if let Ok(stream) = systemd_unix_stream() {
+ stream
+ } else {
+ std_unix_stream(path).await?
+ };
+
+ let serve = Server::builder()
+ .add_service(service)
+ .serve_with_incoming_shutdown(uds_stream, async move {
+ match shutdown_channel.recv().await {
+ Ok(()) => debug!("Unix Socket: Received shutdown signal"),
+ Err(e) => error!("Error receiving shutdown signal {:?}", e),
+ };
+ });
+
+ let socket_path = path.to_path_buf();
+ Ok(tokio::spawn(async move {
+ info!("Listening on {}", socket_path.to_path_buf().display());
+ if let Err(e) = serve.await {
+ eprintln!("Error = {e:?}");
+ }
+ info!(
+ "Shutdown Unix Handler {}",
+ socket_path.to_path_buf().display()
+ );
+ }))
+}
+
+fn systemd_unix_stream() -> anyhow::Result {
+ let listen_fds = libsystemd::activation::receive_descriptors(true)?;
+ if listen_fds.len() == 1 {
+ if let Some(fd) = listen_fds.first() {
+ if !fd.is_unix() {
+ return Err(anyhow!("Wrong Socket"));
+ }
+ let std_listener =
+ unsafe { std::os::unix::net::UnixListener::from_raw_fd(fd.clone().into_raw_fd()) };
+ std_listener.set_nonblocking(true)?;
+ let tokio_listener = UnixListener::from_std(std_listener)?;
+ info!("Using a Unix socket from systemd");
+ return Ok(UnixListenerStream::new(tokio_listener));
+ }
+ }
+
+ Err(anyhow!("Unable to retrieve fd from systemd"))
+}
+
+async fn std_unix_stream(path: &Path) -> anyhow::Result {
+ // Listen on Unix socket
+ if path.exists() {
+ // Attempt to remove the socket, since bind fails if it exists
+ remove_file(path)?;
+ }
+
+ let uds = UnixListener::bind(path)?;
+ let stream = UnixListenerStream::new(uds);
+ // Always set the file permissions of our listening socket.
+ set_file_permissions(path, SOCK_MODE);
+
+ info!("Using default Unix socket");
+ Ok(stream)
+}
diff --git a/bpfman/src/storage.rs b/bpfman-api/src/bin/rpc/storage.rs
similarity index 82%
rename from bpfman/src/storage.rs
rename to bpfman-api/src/bin/rpc/storage.rs
index 3d9685637..cd1c49f41 100644
--- a/bpfman/src/storage.rs
+++ b/bpfman-api/src/bin/rpc/storage.rs
@@ -10,7 +10,11 @@ use std::{
use anyhow::{Context, Result};
use async_trait::async_trait;
use aya::maps::MapData;
-use bpfman_api::util::directories::{RTDIR_BPFMAN_CSI_FS, RTPATH_BPFMAN_CSI_SOCKET};
+use bpfman::{
+ list_programs,
+ types::ListFilter,
+ utils::{create_bpffs, set_dir_permissions, set_file_permissions, SOCK_MODE},
+};
use bpfman_csi::v1::{
identity_server::{Identity, IdentityServer},
node_server::{Node, NodeServer},
@@ -23,30 +27,23 @@ use bpfman_csi::v1::{
NodeUnpublishVolumeResponse, NodeUnstageVolumeRequest, NodeUnstageVolumeResponse, ProbeRequest,
ProbeResponse,
};
-use log::{debug, info, warn};
+use log::{debug, error, info, warn};
use nix::mount::{mount, umount, MsFlags};
-use tokio::{
- net::UnixListener,
- sync::{mpsc, mpsc::Sender, oneshot},
-};
+use tokio::{net::UnixListener, sync::broadcast};
use tokio_stream::wrappers::UnixListenerStream;
use tonic::{transport::Server, Request, Response, Status};
-use crate::{
- command::Command,
- serve::shutdown_handler,
- utils::{create_bpffs, set_dir_permissions, set_file_permissions, SOCK_MODE},
-};
-
const DRIVER_NAME: &str = "csi.bpfman.io";
const MAPS_KEY: &str = "csi.bpfman.io/maps";
const PROGRAM_KEY: &str = "csi.bpfman.io/program";
const OPERATOR_PROGRAM_KEY: &str = "bpfman.io/ProgramName";
+const RTPATH_BPFMAN_CSI_SOCKET: &str = "/run/bpfman/csi/csi.sock";
+const RTDIR_BPFMAN_CSI_FS: &str = "/run/bpfman/csi/fs";
// Node Publish Volume Error code constant mirrored from: https://github.com/container-storage-interface/spec/blob/master/spec.md#nodepublishvolume-errors
const NPV_NOT_FOUND: i32 = 5;
const OWNER_READ_WRITE: u32 = 0o0750;
-pub(crate) struct StorageManager {
+pub struct StorageManager {
csi_identity: CsiIdentity,
csi_node: CsiNode,
}
@@ -58,7 +55,6 @@ struct CsiIdentity {
struct CsiNode {
node_id: String,
- tx: Sender,
}
#[async_trait]
@@ -136,48 +132,29 @@ impl Node for CsiNode {
(Some(m), Some(program_name)) => {
let maps: Vec<&str> = m.split(',').collect();
- // Get the program information from the BpfManager
- let (resp_tx, resp_rx) = oneshot::channel();
- let cmd = Command::List { responder: resp_tx };
-
- // Send the LIST request
- self.tx.send(cmd).await.unwrap();
-
- // Await the response
- let core_map_path = match resp_rx.await {
- Ok(res) => match res {
- // Find the Program with the specified *Program CRD name
- Ok(results) => {
- let prog_data = results
- .into_iter()
- .find(|p| {
- if let Ok(data) = p.data() {
- data.metadata().get(OPERATOR_PROGRAM_KEY)
- == Some(program_name)
- } else {
- false
- }
- })
- .ok_or(Status::new(
- NPV_NOT_FOUND.into(),
- format!("Bpfman Program {program_name} not found"),
- ))?;
- Ok(prog_data
- .data().expect("program data is valid because we just checked it")
- .map_pin_path()
- .expect("map pin path should be set since the program is already loaded")
- .to_owned())
- }
- Err(_) => Err(Status::new(
- NPV_NOT_FOUND.into(),
- format!("Bpfman Program {program_name} not found"),
- )),
- },
- Err(_) => Err(Status::new(
+ // Find the Program with the specified *Program CRD name
+ let prog_data = list_programs(ListFilter::default())
+ .await
+ .map_err(|e| Status::aborted(format!("failed list programs: {e}")))?
+ .into_iter()
+ .find(|p| {
+ p.get_data()
+ .get_metadata()
+ .expect("unable to get program metadata")
+ .get(OPERATOR_PROGRAM_KEY)
+ == Some(program_name)
+ })
+ .ok_or(Status::new(
NPV_NOT_FOUND.into(),
- format!("Unable to list bpfman programs {program_name} not found"),
- )),
- }?;
+ format!("Bpfman Program {program_name} not found"),
+ ))?;
+
+ let core_map_path = prog_data
+ .get_data()
+ .get_map_pin_path()
+ .map_err(|e| Status::aborted(format!("failed to get map_pin_path: {e}")))?
+ .expect("map pin path should be set since the program is already loaded")
+ .to_owned();
// Create the Target Path if it doesn't exist
let target = Path::new(target_path);
@@ -188,7 +165,7 @@ impl Node for CsiNode {
format!("failed creating target path {target_path:?}: {e}"),
)
})?;
- set_dir_permissions(target_path, OWNER_READ_WRITE).await;
+ set_dir_permissions(target_path, OWNER_READ_WRITE);
}
// Make a new bpf fs specifically for the pod.
@@ -214,7 +191,7 @@ impl Node for CsiNode {
// TODO(astoycos) all aya calls should be completed by main bpfManager thread.
maps.iter().try_for_each(|m| {
debug!("Loading map {m} from {core_map_path:?}");
- let mut map = MapData::from_pin(core_map_path.join(m)).map_err(|e| {
+ let map = MapData::from_pin(core_map_path.join(m)).map_err(|e| {
Status::new(
NPV_NOT_FOUND.into(),
format!("map {m} not found in {program_name}'s pinned maps: {e}"),
@@ -342,8 +319,14 @@ impl Node for CsiNode {
}
}
+impl Default for StorageManager {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
impl StorageManager {
- pub fn new(tx: mpsc::Sender) -> Self {
+ pub fn new() -> Self {
const VERSION: &str = env!("CARGO_PKG_VERSION");
let node_id = std::env::var("KUBE_NODE_NAME")
.expect("cannot start bpfman csi driver if KUBE_NODE_NAME not set");
@@ -353,7 +336,7 @@ impl StorageManager {
version: VERSION.to_string(),
};
- let csi_node = CsiNode { node_id, tx };
+ let csi_node = CsiNode { node_id };
Self {
csi_node,
@@ -361,7 +344,11 @@ impl StorageManager {
}
}
- pub async fn run(self) {
+ pub async fn run(self, mut shutdown_channel: broadcast::Receiver<()>) {
+ create_dir_all(RTDIR_BPFMAN_CSI_FS)
+ .context("unable to create CSI socket directory")
+ .expect("cannot create csi filesystem");
+
let path: &Path = Path::new(RTPATH_BPFMAN_CSI_SOCKET);
// Listen on Unix socket
if path.exists() {
@@ -372,14 +359,19 @@ impl StorageManager {
let uds = UnixListener::bind(path)
.unwrap_or_else(|_| panic!("failed to bind {RTPATH_BPFMAN_CSI_SOCKET}"));
let uds_stream = UnixListenerStream::new(uds);
- set_file_permissions(RTPATH_BPFMAN_CSI_SOCKET, SOCK_MODE).await;
+ set_file_permissions(Path::new(RTPATH_BPFMAN_CSI_SOCKET), SOCK_MODE);
let node_service = NodeServer::new(self.csi_node);
let identity_service = IdentityServer::new(self.csi_identity);
let serve = Server::builder()
.add_service(node_service)
.add_service(identity_service)
- .serve_with_incoming_shutdown(uds_stream, shutdown_handler());
+ .serve_with_incoming_shutdown(uds_stream, async move {
+ match shutdown_channel.recv().await {
+ Ok(()) => debug!("Unix Socket: Received shutdown signal"),
+ Err(e) => error!("Error receiving shutdown signal {:?}", e),
+ };
+ });
tokio::spawn(async move {
info!("CSI Plugin Listening on {}", path.display());
@@ -400,11 +392,7 @@ impl StorageManager {
}
#[allow(dead_code)] // TODO: Remove this when the storage manager is fully implemented
- fn pin_map_to_bpffs(
- &self,
- source_object: &mut MapData,
- dest_bpffs: &Path,
- ) -> anyhow::Result<()> {
+ fn pin_map_to_bpffs(&self, source_object: &MapData, dest_bpffs: &Path) -> anyhow::Result<()> {
source_object
.pin(dest_bpffs)
.map_err(|e| anyhow::anyhow!("unable to pin map to bpffs: {}", e))?;
diff --git a/bpfman-api/src/bpfman.v1.rs b/bpfman-api/src/bpfman.v1.rs
index 835f8d3ff..56739c132 100644
--- a/bpfman-api/src/bpfman.v1.rs
+++ b/bpfman-api/src/bpfman.v1.rs
@@ -73,8 +73,10 @@ pub struct ProgramInfo {
#[prost(message, optional, tag = "3")]
pub attach: ::core::option::Option,
#[prost(map = "string, bytes", tag = "4")]
- pub global_data:
- ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::vec::Vec>,
+ pub global_data: ::std::collections::HashMap<
+ ::prost::alloc::string::String,
+ ::prost::alloc::vec::Vec,
+ >,
#[prost(uint32, optional, tag = "5")]
pub map_owner_id: ::core::option::Option,
#[prost(string, tag = "6")]
@@ -82,8 +84,10 @@ pub struct ProgramInfo {
#[prost(string, repeated, tag = "7")]
pub map_used_by: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
#[prost(map = "string, string", tag = "8")]
- pub metadata:
- ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>,
+ pub metadata: ::std::collections::HashMap<
+ ::prost::alloc::string::String,
+ ::prost::alloc::string::String,
+ >,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
@@ -126,8 +130,8 @@ pub struct KprobeAttachInfo {
pub offset: u64,
#[prost(bool, tag = "3")]
pub retprobe: bool,
- #[prost(string, optional, tag = "4")]
- pub namespace: ::core::option::Option<::prost::alloc::string::String>,
+ #[prost(int32, optional, tag = "4")]
+ pub container_pid: ::core::option::Option,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
@@ -142,13 +146,25 @@ pub struct UprobeAttachInfo {
pub retprobe: bool,
#[prost(int32, optional, tag = "5")]
pub pid: ::core::option::Option,
- #[prost(string, optional, tag = "6")]
- pub namespace: ::core::option::Option<::prost::alloc::string::String>,
+ #[prost(int32, optional, tag = "6")]
+ pub container_pid: ::core::option::Option,
+}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct FentryAttachInfo {
+ #[prost(string, tag = "1")]
+ pub fn_name: ::prost::alloc::string::String,
+}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct FexitAttachInfo {
+ #[prost(string, tag = "1")]
+ pub fn_name: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AttachInfo {
- #[prost(oneof = "attach_info::Info", tags = "2, 3, 4, 5, 6")]
+ #[prost(oneof = "attach_info::Info", tags = "2, 3, 4, 5, 6, 7, 8")]
pub info: ::core::option::Option,
}
/// Nested message and enum types in `AttachInfo`.
@@ -166,6 +182,10 @@ pub mod attach_info {
KprobeAttachInfo(super::KprobeAttachInfo),
#[prost(message, tag = "6")]
UprobeAttachInfo(super::UprobeAttachInfo),
+ #[prost(message, tag = "7")]
+ FentryAttachInfo(super::FentryAttachInfo),
+ #[prost(message, tag = "8")]
+ FexitAttachInfo(super::FexitAttachInfo),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
@@ -180,11 +200,15 @@ pub struct LoadRequest {
#[prost(message, optional, tag = "4")]
pub attach: ::core::option::Option,
#[prost(map = "string, string", tag = "5")]
- pub metadata:
- ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>,
+ pub metadata: ::std::collections::HashMap<
+ ::prost::alloc::string::String,
+ ::prost::alloc::string::String,
+ >,
#[prost(map = "string, bytes", tag = "6")]
- pub global_data:
- ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::vec::Vec>,
+ pub global_data: ::std::collections::HashMap<
+ ::prost::alloc::string::String,
+ ::prost::alloc::vec::Vec,
+ >,
#[prost(string, optional, tag = "7")]
pub uuid: ::core::option::Option<::prost::alloc::string::String>,
#[prost(uint32, optional, tag = "8")]
@@ -215,8 +239,10 @@ pub struct ListRequest {
#[prost(bool, optional, tag = "2")]
pub bpfman_programs_only: ::core::option::Option,
#[prost(map = "string, string", tag = "3")]
- pub match_metadata:
- ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>,
+ pub match_metadata: ::std::collections::HashMap<
+ ::prost::alloc::string::String,
+ ::prost::alloc::string::String,
+ >,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
@@ -261,8 +287,8 @@ pub struct GetResponse {
/// Generated client implementations.
pub mod bpfman_client {
#![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)]
- use tonic::codegen::http::Uri;
use tonic::codegen::*;
+ use tonic::codegen::http::Uri;
#[derive(Debug, Clone)]
pub struct BpfmanClient {
inner: tonic::client::Grpc,
@@ -295,8 +321,9 @@ pub mod bpfman_client {
>::ResponseBody,
>,
>,
- >>::Error:
- Into + Send + Sync,
+ ,
+ >>::Error: Into + Send + Sync,
{
BpfmanClient::new(InterceptedService::new(inner, interceptor))
}
@@ -335,66 +362,79 @@ pub mod bpfman_client {
&mut self,
request: impl tonic::IntoRequest,
) -> std::result::Result, tonic::Status> {
- self.inner.ready().await.map_err(|e| {
- tonic::Status::new(
- tonic::Code::Unknown,
- format!("Service was not ready: {}", e.into()),
- )
- })?;
+ self.inner
+ .ready()
+ .await
+ .map_err(|e| {
+ tonic::Status::new(
+ tonic::Code::Unknown,
+ format!("Service was not ready: {}", e.into()),
+ )
+ })?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static("/bpfman.v1.Bpfman/Load");
let mut req = request.into_request();
- req.extensions_mut()
- .insert(GrpcMethod::new("bpfman.v1.Bpfman", "Load"));
+ req.extensions_mut().insert(GrpcMethod::new("bpfman.v1.Bpfman", "Load"));
self.inner.unary(req, path, codec).await
}
pub async fn unload(
&mut self,
request: impl tonic::IntoRequest,
) -> std::result::Result, tonic::Status> {
- self.inner.ready().await.map_err(|e| {
- tonic::Status::new(
- tonic::Code::Unknown,
- format!("Service was not ready: {}", e.into()),
- )
- })?;
+ self.inner
+ .ready()
+ .await
+ .map_err(|e| {
+ tonic::Status::new(
+ tonic::Code::Unknown,
+ format!("Service was not ready: {}", e.into()),
+ )
+ })?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static("/bpfman.v1.Bpfman/Unload");
let mut req = request.into_request();
- req.extensions_mut()
- .insert(GrpcMethod::new("bpfman.v1.Bpfman", "Unload"));
+ req.extensions_mut().insert(GrpcMethod::new("bpfman.v1.Bpfman", "Unload"));
self.inner.unary(req, path, codec).await
}
pub async fn list(
&mut self,
request: impl tonic::IntoRequest,
) -> std::result::Result, tonic::Status> {
- self.inner.ready().await.map_err(|e| {
- tonic::Status::new(
- tonic::Code::Unknown,
- format!("Service was not ready: {}", e.into()),
- )
- })?;
+ self.inner
+ .ready()
+ .await
+ .map_err(|e| {
+ tonic::Status::new(
+ tonic::Code::Unknown,
+ format!("Service was not ready: {}", e.into()),
+ )
+ })?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static("/bpfman.v1.Bpfman/List");
let mut req = request.into_request();
- req.extensions_mut()
- .insert(GrpcMethod::new("bpfman.v1.Bpfman", "List"));
+ req.extensions_mut().insert(GrpcMethod::new("bpfman.v1.Bpfman", "List"));
self.inner.unary(req, path, codec).await
}
pub async fn pull_bytecode(
&mut self,
request: impl tonic::IntoRequest,
- ) -> std::result::Result, tonic::Status>
- {
- self.inner.ready().await.map_err(|e| {
- tonic::Status::new(
- tonic::Code::Unknown,
- format!("Service was not ready: {}", e.into()),
- )
- })?;
+ ) -> std::result::Result<
+ tonic::Response,
+ tonic::Status,
+ > {
+ self.inner
+ .ready()
+ .await
+ .map_err(|e| {
+ tonic::Status::new(
+ tonic::Code::Unknown,
+ format!("Service was not ready: {}", e.into()),
+ )
+ })?;
let codec = tonic::codec::ProstCodec::default();
- let path = http::uri::PathAndQuery::from_static("/bpfman.v1.Bpfman/PullBytecode");
+ let path = http::uri::PathAndQuery::from_static(
+ "/bpfman.v1.Bpfman/PullBytecode",
+ );
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("bpfman.v1.Bpfman", "PullBytecode"));
@@ -404,17 +444,19 @@ pub mod bpfman_client {
&mut self,
request: impl tonic::IntoRequest,
) -> std::result::Result, tonic::Status> {
- self.inner.ready().await.map_err(|e| {
- tonic::Status::new(
- tonic::Code::Unknown,
- format!("Service was not ready: {}", e.into()),
- )
- })?;
+ self.inner
+ .ready()
+ .await
+ .map_err(|e| {
+ tonic::Status::new(
+ tonic::Code::Unknown,
+ format!("Service was not ready: {}", e.into()),
+ )
+ })?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static("/bpfman.v1.Bpfman/Get");
let mut req = request.into_request();
- req.extensions_mut()
- .insert(GrpcMethod::new("bpfman.v1.Bpfman", "Get"));
+ req.extensions_mut().insert(GrpcMethod::new("bpfman.v1.Bpfman", "Get"));
self.inner.unary(req, path, codec).await
}
}
@@ -441,7 +483,10 @@ pub mod bpfman_server {
async fn pull_bytecode(
&self,
request: tonic::Request,
- ) -> std::result::Result, tonic::Status>;
+ ) -> std::result::Result<
+ tonic::Response,
+ tonic::Status,
+ >;
async fn get(
&self,
request: tonic::Request,
@@ -470,7 +515,10 @@ pub mod bpfman_server {
max_encoding_message_size: None,
}
}
- pub fn with_interceptor(inner: T, interceptor: F) -> InterceptedService
+ pub fn with_interceptor(
+ inner: T,
+ interceptor: F,
+ ) -> InterceptedService
where
F: tonic::service::Interceptor,
{
@@ -526,15 +574,21 @@ pub mod bpfman_server {
"/bpfman.v1.Bpfman/Load" => {
#[allow(non_camel_case_types)]
struct LoadSvc(pub Arc);
- impl tonic::server::UnaryService for LoadSvc {
+ impl tonic::server::UnaryService
+ for LoadSvc {
type Response = super::LoadResponse;
- type Future = BoxFuture, tonic::Status>;
+ type Future = BoxFuture<
+ tonic::Response,
+ tonic::Status,
+ >;
fn call(
&mut self,
request: tonic::Request,
) -> Self::Future {
let inner = Arc::clone(&self.0);
- let fut = async move { ::load(&inner, request).await };
+ let fut = async move {
+ ::load(&inner, request).await
+ };
Box::pin(fut)
}
}
@@ -564,15 +618,21 @@ pub mod bpfman_server {
"/bpfman.v1.Bpfman/Unload" => {
#[allow(non_camel_case_types)]
struct UnloadSvc(pub Arc);
- impl tonic::server::UnaryService for UnloadSvc {
+ impl tonic::server::UnaryService
+ for UnloadSvc {
type Response = super::UnloadResponse;
- type Future = BoxFuture, tonic::Status>;
+ type Future = BoxFuture<
+ tonic::Response,
+ tonic::Status,
+ >;
fn call(
&mut self,
request: tonic::Request,
) -> Self::Future {
let inner = Arc::clone(&self.0);
- let fut = async move { ::unload(&inner, request).await };
+ let fut = async move {
+ ::unload(&inner, request).await
+ };
Box::pin(fut)
}
}
@@ -602,15 +662,21 @@ pub mod bpfman_server {
"/bpfman.v1.Bpfman/List" => {
#[allow(non_camel_case_types)]
struct ListSvc(pub Arc);
- impl tonic::server::UnaryService for ListSvc {
+ impl tonic::server::UnaryService
+ for ListSvc {
type Response = super::ListResponse;
- type Future = BoxFuture, tonic::Status>;
+ type Future = BoxFuture<
+ tonic::Response,
+ tonic::Status,
+ >;
fn call(
&mut self,
request: tonic::Request,
) -> Self::Future {
let inner = Arc::clone(&self.0);
- let fut = async move { ::list(&inner, request).await };
+ let fut = async move {
+ ::list(&inner, request).await
+ };
Box::pin(fut)
}
}
@@ -640,16 +706,23 @@ pub mod bpfman_server {
"/bpfman.v1.Bpfman/PullBytecode" => {
#[allow(non_camel_case_types)]
struct PullBytecodeSvc(pub Arc);
- impl tonic::server::UnaryService for PullBytecodeSvc {
+ impl<
+ T: Bpfman,
+ > tonic::server::UnaryService
+ for PullBytecodeSvc {
type Response = super::PullBytecodeResponse;
- type Future = BoxFuture, tonic::Status>;
+ type Future = BoxFuture<
+ tonic::Response,
+ tonic::Status,
+ >;
fn call(
&mut self,
request: tonic::Request,
) -> Self::Future {
let inner = Arc::clone(&self.0);
- let fut =
- async move { ::pull_bytecode(&inner, request).await };
+ let fut = async move {
+ ::pull_bytecode(&inner, request).await
+ };
Box::pin(fut)
}
}
@@ -679,15 +752,21 @@ pub mod bpfman_server {
"/bpfman.v1.Bpfman/Get" => {
#[allow(non_camel_case_types)]
struct GetSvc(pub Arc);
- impl tonic::server::UnaryService for GetSvc {
+ impl tonic::server::UnaryService
+ for GetSvc {
type Response = super::GetResponse;
- type Future = BoxFuture, tonic::Status>;
+ type Future = BoxFuture<
+ tonic::Response,
+ tonic::Status,
+ >;
fn call(
&mut self,
request: tonic::Request,
) -> Self::Future {
let inner = Arc::clone(&self.0);
- let fut = async move { ::get(&inner, request).await };
+ let fut = async move {
+ ::get(&inner, request).await
+ };
Box::pin(fut)
}
}
@@ -714,14 +793,18 @@ pub mod bpfman_server {
};
Box::pin(fut)
}
- _ => Box::pin(async move {
- Ok(http::Response::builder()
- .status(200)
- .header("grpc-status", "12")
- .header("content-type", "application/grpc")
- .body(empty_body())
- .unwrap())
- }),
+ _ => {
+ Box::pin(async move {
+ Ok(
+ http::Response::builder()
+ .status(200)
+ .header("grpc-status", "12")
+ .header("content-type", "application/grpc")
+ .body(empty_body())
+ .unwrap(),
+ )
+ })
+ }
}
}
}
diff --git a/bpfman-api/src/config.rs b/bpfman-api/src/config.rs
deleted file mode 100644
index eeb64aa7c..000000000
--- a/bpfman-api/src/config.rs
+++ /dev/null
@@ -1,273 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-// Copyright Authors of bpfman
-
-use std::{collections::HashMap, str::FromStr};
-
-use aya::programs::XdpFlags;
-use serde::{Deserialize, Serialize};
-use thiserror::Error;
-
-use crate::util::directories::*;
-
-#[derive(Debug, Deserialize, Default, Clone)]
-pub struct Config {
- pub interfaces: Option>,
- #[serde(default)]
- pub grpc: Grpc,
- pub signing: Option,
-}
-
-#[derive(Debug, Deserialize, Clone)]
-pub struct SigningConfig {
- pub allow_unsigned: bool,
-}
-
-impl Default for SigningConfig {
- fn default() -> Self {
- Self {
- // Allow unsigned programs by default
- allow_unsigned: true,
- }
- }
-}
-
-#[derive(Debug, Error)]
-pub enum ConfigError {
- #[error("Error parsing config file: {0}")]
- ParseError(#[from] toml::de::Error),
-}
-
-impl FromStr for Config {
- type Err = ConfigError;
-
- fn from_str(s: &str) -> Result {
- toml::from_str(s).map_err(ConfigError::ParseError)
- }
-}
-
-#[derive(Debug, Deserialize, Copy, Clone)]
-pub struct InterfaceConfig {
- pub xdp_mode: XdpMode,
-}
-
-#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq)]
-#[serde(rename_all = "lowercase")]
-pub enum XdpMode {
- Skb,
- Drv,
- Hw,
-}
-
-impl XdpMode {
- pub fn as_flags(&self) -> XdpFlags {
- match self {
- XdpMode::Skb => XdpFlags::SKB_MODE,
- XdpMode::Drv => XdpFlags::DRV_MODE,
- XdpMode::Hw => XdpFlags::HW_MODE,
- }
- }
-}
-
-impl ToString for XdpMode {
- fn to_string(&self) -> String {
- match self {
- XdpMode::Skb => "skb".to_string(),
- XdpMode::Drv => "drv".to_string(),
- XdpMode::Hw => "hw".to_string(),
- }
- }
-}
-
-#[derive(Debug, Deserialize, Clone)]
-pub struct Grpc {
- #[serde(default)]
- pub endpoints: Vec,
-}
-
-impl Default for Grpc {
- fn default() -> Self {
- Self {
- endpoints: vec![Endpoint::default()],
- }
- }
-}
-
-#[derive(Debug, Deserialize, Clone)]
-#[serde(tag = "type", rename_all = "lowercase")]
-pub enum Endpoint {
- Unix {
- #[serde(default = "default_unix")]
- path: String,
- #[serde(default = "default_enabled")]
- enabled: bool,
- },
-}
-
-impl Default for Endpoint {
- fn default() -> Self {
- Endpoint::Unix {
- path: default_unix(),
- enabled: default_enabled(),
- }
- }
-}
-
-fn default_unix() -> String {
- RTPATH_BPFMAN_SOCKET.to_string()
-}
-
-fn default_enabled() -> bool {
- true
-}
-
-#[cfg(test)]
-mod test {
- use super::*;
-
- #[test]
- fn test_config_from_invalid_string() {
- assert!(Config::from_str("i am a teapot").is_err());
- }
-
- #[test]
- fn test_config_single_iface() {
- let input = r#"
- [interfaces]
- [interfaces.eth0]
- xdp_mode = "drv"
- "#;
- let config: Config = toml::from_str(input).expect("error parsing toml input");
- match config.interfaces {
- Some(i) => {
- assert!(i.contains_key("eth0"));
- assert_eq!(i.get("eth0").unwrap().xdp_mode, XdpMode::Drv)
- }
- None => panic!("expected interfaces to be present"),
- }
- }
-
- #[test]
- fn test_config_multiple_iface() {
- let input = r#"
- [interfaces]
- [interfaces.eth0]
- xdp_mode = "drv"
- [interfaces.eth1]
- xdp_mode = "hw"
- [interfaces.eth2]
- xdp_mode = "skb"
- "#;
- let config: Config = toml::from_str(input).expect("error parsing toml input");
- match config.interfaces {
- Some(i) => {
- assert_eq!(i.len(), 3);
- assert!(i.contains_key("eth0"));
- assert_eq!(i.get("eth0").unwrap().xdp_mode, XdpMode::Drv);
- assert!(i.contains_key("eth1"));
- assert_eq!(i.get("eth1").unwrap().xdp_mode, XdpMode::Hw);
- assert!(i.contains_key("eth2"));
- assert_eq!(i.get("eth2").unwrap().xdp_mode, XdpMode::Skb);
- }
- None => panic!("expected interfaces to be present"),
- }
- }
-
- #[test]
- fn test_config_endpoint_default() {
- let input = r#"
- "#;
-
- let config: Config = toml::from_str(input).expect("error parsing toml input");
- let endpoints = config.grpc.endpoints;
- assert_eq!(endpoints.len(), 1);
-
- match endpoints.get(0).unwrap() {
- Endpoint::Unix { path, enabled } => {
- assert_eq!(path, &default_unix());
- assert_eq!(enabled, &true);
- }
- }
- }
-
- #[test]
- fn test_config_endpoint_unix_default() {
- let input = r#"
- [[grpc.endpoints]]
- type = "unix"
- "#;
-
- let config: Config = toml::from_str(input).expect("error parsing toml input");
- let endpoints = config.grpc.endpoints;
- assert_eq!(endpoints.len(), 1);
-
- match endpoints.get(0).unwrap() {
- Endpoint::Unix { path, enabled } => {
- assert_eq!(path, &default_unix());
- assert!(enabled);
- }
- }
- }
-
- #[test]
- fn test_config_endpoint_unix() {
- let input = r#"
- [[grpc.endpoints]]
- type = "unix"
- path = "/tmp/socket"
- "#;
-
- let config: Config = toml::from_str(input).expect("error parsing toml input");
- let endpoints = config.grpc.endpoints;
- assert_eq!(endpoints.len(), 1);
-
- match endpoints.get(0).unwrap() {
- Endpoint::Unix { path, enabled } => {
- assert_eq!(path, "/tmp/socket");
- assert!(enabled);
- }
- }
- }
-
- #[test]
- fn test_config_endpoint() {
- let input = r#"
- [[grpc.endpoints]]
- type = "unix"
- enabled = true
- path = "/run/bpfman/bpfman.sock"
-
- [[grpc.endpoints]]
- type = "unix"
- enabled = true
- path = "/run/bpfman/bpfman2.sock"
- "#;
-
- let expected_endpoints: Vec = vec![
- Endpoint::Unix {
- path: String::from("/run/bpfman/bpfman.sock"),
- enabled: true,
- },
- Endpoint::Unix {
- path: String::from("/run/bpfman/bpfman2.sock"),
- enabled: true,
- },
- ];
-
- let config: Config = toml::from_str(input).expect("error parsing toml input");
- let endpoints = config.grpc.endpoints;
- assert_eq!(endpoints.len(), 2);
-
- for (i, endpoint) in endpoints.iter().enumerate() {
- match endpoint {
- Endpoint::Unix { path, enabled } => {
- let Endpoint::Unix {
- path: expected_path,
- enabled: expected_enabled,
- } = expected_endpoints.get(i).unwrap();
- assert_eq!(path, expected_path);
- assert_eq!(enabled, expected_enabled);
- }
- }
- }
- }
-}
diff --git a/bpfman-api/src/lib.rs b/bpfman-api/src/lib.rs
index c706bbd3a..3cae70666 100644
--- a/bpfman-api/src/lib.rs
+++ b/bpfman-api/src/lib.rs
@@ -1,585 +1,155 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of bpfman
-pub mod config;
-pub mod util;
+use bpfman::{
+ errors::BpfmanError,
+ types::{BytecodeImage, Location, Program},
+};
+
+use crate::v1::{
+ attach_info::Info, bytecode_location::Location as V1Location, AttachInfo,
+ BytecodeImage as V1BytecodeImage, BytecodeLocation, FentryAttachInfo, FexitAttachInfo,
+ KernelProgramInfo as V1KernelProgramInfo, KprobeAttachInfo, ProgramInfo,
+ ProgramInfo as V1ProgramInfo, TcAttachInfo, TracepointAttachInfo, UprobeAttachInfo,
+ XdpAttachInfo,
+};
+
#[path = "bpfman.v1.rs"]
#[rustfmt::skip]
#[allow(clippy::all)]
pub mod v1;
-use clap::ValueEnum;
-use serde::{Deserialize, Serialize};
-use thiserror::Error;
-use url::ParseError as urlParseError;
-use v1::bytecode_location::Location;
-
-#[derive(Error, Debug)]
-pub enum ParseError {
- #[error("{program} is not a valid program type")]
- InvalidProgramType { program: String },
- #[error("{proceedon} is not a valid proceed-on value")]
- InvalidProceedOn { proceedon: String },
- #[error("not a valid direction: {direction}")]
- InvalidDirection { direction: String },
- #[error("Failed to Parse bytecode location: {0}")]
- BytecodeLocationParseFailure(#[source] urlParseError),
- #[error("Invalid bytecode location: {location}")]
- InvalidBytecodeLocation { location: String },
- #[error("Invalid bytecode image pull policy: {pull_policy}")]
- InvalidBytecodeImagePullPolicy { pull_policy: String },
- #[error("{probe} is not a valid probe type")]
- InvalidProbeType { probe: String },
-}
-
-#[derive(ValueEnum, Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
-pub enum ProgramType {
- Unspec,
- SocketFilter,
- Probe, // kprobe, kretprobe, uprobe, uretprobe
- Tc,
- SchedAct,
- Tracepoint,
- Xdp,
- PerfEvent,
- CgroupSkb,
- CgroupSock,
- LwtIn,
- LwtOut,
- LwtXmit,
- SockOps,
- SkSkb,
- CgroupDevice,
- SkMsg,
- RawTracepoint,
- CgroupSockAddr,
- LwtSeg6Local,
- LircMode2,
- SkReuseport,
- FlowDissector,
- CgroupSysctl,
- RawTracepointWritable,
- CgroupSockopt,
- Tracing,
- StructOps,
- Ext,
- Lsm,
- SkLookup,
- Syscall,
-}
-
-impl TryFrom for ProgramType {
- type Error = ParseError;
-
- fn try_from(value: String) -> Result {
- Ok(match value.as_str() {
- "unspec" => ProgramType::Unspec,
- "socket_filter" => ProgramType::SocketFilter,
- "probe" => ProgramType::Probe,
- "tc" => ProgramType::Tc,
- "sched_act" => ProgramType::SchedAct,
- "tracepoint" => ProgramType::Tracepoint,
- "xdp" => ProgramType::Xdp,
- "perf_event" => ProgramType::PerfEvent,
- "cgroup_skb" => ProgramType::CgroupSkb,
- "cgroup_sock" => ProgramType::CgroupSock,
- "lwt_in" => ProgramType::LwtIn,
- "lwt_out" => ProgramType::LwtOut,
- "lwt_xmit" => ProgramType::LwtXmit,
- "sock_ops" => ProgramType::SockOps,
- "sk_skb" => ProgramType::SkSkb,
- "cgroup_device" => ProgramType::CgroupDevice,
- "sk_msg" => ProgramType::SkMsg,
- "raw_tracepoint" => ProgramType::RawTracepoint,
- "cgroup_sock_addr" => ProgramType::CgroupSockAddr,
- "lwt_seg6local" => ProgramType::LwtSeg6Local,
- "lirc_mode2" => ProgramType::LircMode2,
- "sk_reuseport" => ProgramType::SkReuseport,
- "flow_dissector" => ProgramType::FlowDissector,
- "cgroup_sysctl" => ProgramType::CgroupSysctl,
- "raw_tracepoint_writable" => ProgramType::RawTracepointWritable,
- "cgroup_sockopt" => ProgramType::CgroupSockopt,
- "tracing" => ProgramType::Tracing,
- "struct_ops" => ProgramType::StructOps,
- "ext" => ProgramType::Ext,
- "lsm" => ProgramType::Lsm,
- "sk_lookup" => ProgramType::SkLookup,
- "syscall" => ProgramType::Syscall,
- other => {
- return Err(ParseError::InvalidProgramType {
- program: other.to_string(),
- })
- }
- })
- }
-}
-
-impl TryFrom for ProgramType {
- type Error = ParseError;
- fn try_from(value: u32) -> Result {
- Ok(match value {
- 0 => ProgramType::Unspec,
- 1 => ProgramType::SocketFilter,
- 2 => ProgramType::Probe,
- 3 => ProgramType::Tc,
- 4 => ProgramType::SchedAct,
- 5 => ProgramType::Tracepoint,
- 6 => ProgramType::Xdp,
- 7 => ProgramType::PerfEvent,
- 8 => ProgramType::CgroupSkb,
- 9 => ProgramType::CgroupSock,
- 10 => ProgramType::LwtIn,
- 11 => ProgramType::LwtOut,
- 12 => ProgramType::LwtXmit,
- 13 => ProgramType::SockOps,
- 14 => ProgramType::SkSkb,
- 15 => ProgramType::CgroupDevice,
- 16 => ProgramType::SkMsg,
- 17 => ProgramType::RawTracepoint,
- 18 => ProgramType::CgroupSockAddr,
- 19 => ProgramType::LwtSeg6Local,
- 20 => ProgramType::LircMode2,
- 21 => ProgramType::SkReuseport,
- 22 => ProgramType::FlowDissector,
- 23 => ProgramType::CgroupSysctl,
- 24 => ProgramType::RawTracepointWritable,
- 25 => ProgramType::CgroupSockopt,
- 26 => ProgramType::Tracing,
- 27 => ProgramType::StructOps,
- 28 => ProgramType::Ext,
- 29 => ProgramType::Lsm,
- 30 => ProgramType::SkLookup,
- 31 => ProgramType::Syscall,
- other => {
- return Err(ParseError::InvalidProgramType {
- program: other.to_string(),
+impl TryFrom<&Program> for ProgramInfo {
+ type Error = BpfmanError;
+
+ fn try_from(program: &Program) -> Result {
+ let data = program.get_data();
+
+ let bytecode = match data.get_location()? {
+ Location::Image(m) => {
+ Some(BytecodeLocation {
+ location: Some(V1Location::Image(V1BytecodeImage {
+ url: m.get_url().to_string(),
+ image_pull_policy: m.get_pull_policy().to_owned() as i32,
+ // Never dump Plaintext Credentials
+ username: Some(String::new()),
+ password: Some(String::new()),
+ })),
})
}
- })
- }
-}
-
-impl std::fmt::Display for ProgramType {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- let v = match self {
- ProgramType::Unspec => "unspec",
- ProgramType::SocketFilter => "socket_filter",
- ProgramType::Probe => "probe",
- ProgramType::Tc => "tc",
- ProgramType::SchedAct => "sched_act",
- ProgramType::Tracepoint => "tracepoint",
- ProgramType::Xdp => "xdp",
- ProgramType::PerfEvent => "perf_event",
- ProgramType::CgroupSkb => "cgroup_skb",
- ProgramType::CgroupSock => "cgroup_sock",
- ProgramType::LwtIn => "lwt_in",
- ProgramType::LwtOut => "lwt_out",
- ProgramType::LwtXmit => "lwt_xmit",
- ProgramType::SockOps => "sock_ops",
- ProgramType::SkSkb => "sk_skb",
- ProgramType::CgroupDevice => "cgroup_device",
- ProgramType::SkMsg => "sk_msg",
- ProgramType::RawTracepoint => "raw_tracepoint",
- ProgramType::CgroupSockAddr => "cgroup_sock_addr",
- ProgramType::LwtSeg6Local => "lwt_seg6local",
- ProgramType::LircMode2 => "lirc_mode2",
- ProgramType::SkReuseport => "sk_reuseport",
- ProgramType::FlowDissector => "flow_dissector",
- ProgramType::CgroupSysctl => "cgroup_sysctl",
- ProgramType::RawTracepointWritable => "raw_tracepoint_writable",
- ProgramType::CgroupSockopt => "cgroup_sockopt",
- ProgramType::Tracing => "tracing",
- ProgramType::StructOps => "struct_ops",
- ProgramType::Ext => "ext",
- ProgramType::Lsm => "lsm",
- ProgramType::SkLookup => "sk_lookup",
- ProgramType::Syscall => "syscall",
+ Location::File(m) => Some(BytecodeLocation {
+ location: Some(V1Location::File(m.to_string())),
+ }),
};
- write!(f, "{v}")
- }
-}
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
-pub enum ProbeType {
- Kprobe,
- Kretprobe,
- Uprobe,
- Uretprobe,
-}
-
-impl TryFrom for ProbeType {
- type Error = ParseError;
-
- fn try_from(value: i32) -> Result {
- Ok(match value {
- 0 => ProbeType::Kprobe,
- 1 => ProbeType::Kretprobe,
- 2 => ProbeType::Uprobe,
- 3 => ProbeType::Uretprobe,
- other => {
- return Err(ParseError::InvalidProbeType {
- probe: other.to_string(),
- })
- }
- })
- }
-}
-impl From for ProbeType {
- fn from(value: aya::programs::ProbeKind) -> Self {
- match value {
- aya::programs::ProbeKind::KProbe => ProbeType::Kprobe,
- aya::programs::ProbeKind::KRetProbe => ProbeType::Kretprobe,
- aya::programs::ProbeKind::UProbe => ProbeType::Uprobe,
- aya::programs::ProbeKind::URetProbe => ProbeType::Uretprobe,
- }
- }
-}
-
-impl std::fmt::Display for ProbeType {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- let v = match self {
- ProbeType::Kprobe => "kprobe",
- ProbeType::Kretprobe => "kretprobe",
- ProbeType::Uprobe => "uprobe",
- ProbeType::Uretprobe => "uretprobe",
+ let attach_info = AttachInfo {
+ info: match program.clone() {
+ Program::Xdp(p) => Some(Info::XdpAttachInfo(XdpAttachInfo {
+ priority: p.get_priority()?,
+ iface: p.get_iface()?.to_string(),
+ position: p.get_current_position()?.unwrap_or(0) as i32,
+ proceed_on: p.get_proceed_on()?.as_action_vec(),
+ })),
+ Program::Tc(p) => Some(Info::TcAttachInfo(TcAttachInfo {
+ priority: p.get_priority()?,
+ iface: p.get_iface()?.to_string(),
+ position: p.get_current_position()?.unwrap_or(0) as i32,
+ direction: p.get_direction()?.to_string(),
+ proceed_on: p.get_proceed_on()?.as_action_vec(),
+ })),
+ Program::Tracepoint(p) => Some(Info::TracepointAttachInfo(TracepointAttachInfo {
+ tracepoint: p.get_tracepoint()?.to_string(),
+ })),
+ Program::Kprobe(p) => Some(Info::KprobeAttachInfo(KprobeAttachInfo {
+ fn_name: p.get_fn_name()?.to_string(),
+ offset: p.get_offset()?,
+ retprobe: p.get_retprobe()?,
+ container_pid: p.get_container_pid()?,
+ })),
+ Program::Uprobe(p) => Some(Info::UprobeAttachInfo(UprobeAttachInfo {
+ fn_name: p.get_fn_name()?.map(|v| v.to_string()),
+ offset: p.get_offset()?,
+ target: p.get_target()?.to_string(),
+ retprobe: p.get_retprobe()?,
+ pid: p.get_pid()?,
+ container_pid: p.get_container_pid()?,
+ })),
+ Program::Fentry(p) => Some(Info::FentryAttachInfo(FentryAttachInfo {
+ fn_name: p.get_fn_name()?.to_string(),
+ })),
+ Program::Fexit(p) => Some(Info::FexitAttachInfo(FexitAttachInfo {
+ fn_name: p.get_fn_name()?.to_string(),
+ })),
+ Program::Unsupported(_) => None,
+ },
};
- write!(f, "{v}")
- }
-}
-
-#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
-pub enum XdpProceedOnEntry {
- Aborted,
- Drop,
- Pass,
- Tx,
- Redirect,
- DispatcherReturn = 31,
-}
-impl TryFrom for XdpProceedOnEntry {
- type Error = ParseError;
- fn try_from(value: String) -> Result {
- Ok(match value.as_str() {
- "aborted" => XdpProceedOnEntry::Aborted,
- "drop" => XdpProceedOnEntry::Drop,
- "pass" => XdpProceedOnEntry::Pass,
- "tx" => XdpProceedOnEntry::Tx,
- "redirect" => XdpProceedOnEntry::Redirect,
- "dispatcher_return" => XdpProceedOnEntry::DispatcherReturn,
- proceedon => {
- return Err(ParseError::InvalidProceedOn {
- proceedon: proceedon.to_string(),
- })
- }
+ // Populate the Program Info with bpfman data
+ Ok(V1ProgramInfo {
+ name: data.get_name()?.to_string(),
+ bytecode,
+ attach: Some(attach_info),
+ global_data: data.get_global_data()?,
+ map_owner_id: data.get_map_owner_id()?,
+ map_pin_path: data
+ .get_map_pin_path()?
+ .map_or(String::new(), |v| v.to_str().unwrap().to_string()),
+ map_used_by: data
+ .get_maps_used_by()?
+ .iter()
+ .map(|m| m.to_string())
+ .collect(),
+ metadata: data.get_metadata()?,
})
}
}
-impl TryFrom for XdpProceedOnEntry {
- type Error = ParseError;
- fn try_from(value: i32) -> Result {
- Ok(match value {
- 0 => XdpProceedOnEntry::Aborted,
- 1 => XdpProceedOnEntry::Drop,
- 2 => XdpProceedOnEntry::Pass,
- 3 => XdpProceedOnEntry::Tx,
- 4 => XdpProceedOnEntry::Redirect,
- 31 => XdpProceedOnEntry::DispatcherReturn,
- proceedon => {
- return Err(ParseError::InvalidProceedOn {
- proceedon: proceedon.to_string(),
- })
- }
- })
- }
-}
-
-impl std::fmt::Display for XdpProceedOnEntry {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- let v = match self {
- XdpProceedOnEntry::Aborted => "aborted",
- XdpProceedOnEntry::Drop => "drop",
- XdpProceedOnEntry::Pass => "pass",
- XdpProceedOnEntry::Tx => "tx",
- XdpProceedOnEntry::Redirect => "redirect",
- XdpProceedOnEntry::DispatcherReturn => "dispatcher_return",
- };
- write!(f, "{v}")
- }
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug)]
-pub struct XdpProceedOn(Vec);
-impl Default for XdpProceedOn {
- fn default() -> Self {
- XdpProceedOn(vec![
- XdpProceedOnEntry::Pass,
- XdpProceedOnEntry::DispatcherReturn,
- ])
- }
-}
-
-impl XdpProceedOn {
- pub fn from_strings>(values: T) -> Result {
- let entries = values.as_ref();
- let mut res = vec![];
- for e in entries {
- res.push(e.to_owned().try_into()?)
- }
- Ok(XdpProceedOn(res))
- }
-
- pub fn from_int32s>(values: T) -> Result {
- let entries = values.as_ref();
- if entries.is_empty() {
- return Ok(XdpProceedOn::default());
- }
- let mut res = vec![];
- for e in entries {
- res.push((*e).try_into()?)
- }
- Ok(XdpProceedOn(res))
- }
-
- pub fn mask(&self) -> u32 {
- let mut proceed_on_mask: u32 = 0;
- for action in self.0.clone().into_iter() {
- proceed_on_mask |= 1 << action as u32;
- }
- proceed_on_mask
- }
-
- pub fn as_action_vec(&self) -> Vec {
- let mut res = vec![];
- for entry in &self.0 {
- res.push((*entry) as i32)
- }
- res
- }
-}
-
-impl std::fmt::Display for XdpProceedOn {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- let res: Vec = self.0.iter().map(|x| x.to_string()).collect();
- write!(f, "{}", res.join(", "))
- }
-}
-
-#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
-pub enum TcProceedOnEntry {
- Unspec = -1,
- Ok = 0,
- Reclassify,
- Shot,
- Pipe,
- Stolen,
- Queued,
- Repeat,
- Redirect,
- Trap,
- DispatcherReturn = 30,
-}
-
-impl TryFrom for TcProceedOnEntry {
- type Error = ParseError;
- fn try_from(value: String) -> Result {
- Ok(match value.as_str() {
- "unspec" => TcProceedOnEntry::Unspec,
- "ok" => TcProceedOnEntry::Ok,
- "reclassify" => TcProceedOnEntry::Reclassify,
- "shot" => TcProceedOnEntry::Shot,
- "pipe" => TcProceedOnEntry::Pipe,
- "stolen" => TcProceedOnEntry::Stolen,
- "queued" => TcProceedOnEntry::Queued,
- "repeat" => TcProceedOnEntry::Repeat,
- "redirect" => TcProceedOnEntry::Redirect,
- "trap" => TcProceedOnEntry::Trap,
- "dispatcher_return" => TcProceedOnEntry::DispatcherReturn,
- proceedon => {
- return Err(ParseError::InvalidProceedOn {
- proceedon: proceedon.to_string(),
- })
- }
+impl TryFrom<&Program> for V1KernelProgramInfo {
+ type Error = BpfmanError;
+
+ fn try_from(program: &Program) -> Result {
+ // Get the Kernel Info.
+ let data = program.get_data();
+
+ // Populate the Kernel Info.
+ Ok(V1KernelProgramInfo {
+ id: data.get_id()?,
+ name: data.get_kernel_name()?.to_string(),
+ program_type: program.kind() as u32,
+ loaded_at: data.get_kernel_loaded_at()?.to_string(),
+ tag: data.get_kernel_tag()?.to_string(),
+ gpl_compatible: data.get_kernel_gpl_compatible()?,
+ map_ids: data.get_kernel_map_ids()?,
+ btf_id: data.get_kernel_btf_id()?,
+ bytes_xlated: data.get_kernel_bytes_xlated()?,
+ jited: data.get_kernel_jited()?,
+ bytes_jited: data.get_kernel_bytes_jited()?,
+ bytes_memlock: data.get_kernel_bytes_memlock()?,
+ verified_insns: data.get_kernel_verified_insns()?,
})
}
}
-impl TryFrom for TcProceedOnEntry {
- type Error = ParseError;
- fn try_from(value: i32) -> Result {
- Ok(match value {
- -1 => TcProceedOnEntry::Unspec,
- 0 => TcProceedOnEntry::Ok,
- 1 => TcProceedOnEntry::Reclassify,
- 2 => TcProceedOnEntry::Shot,
- 3 => TcProceedOnEntry::Pipe,
- 4 => TcProceedOnEntry::Stolen,
- 5 => TcProceedOnEntry::Queued,
- 6 => TcProceedOnEntry::Repeat,
- 7 => TcProceedOnEntry::Redirect,
- 8 => TcProceedOnEntry::Trap,
- 30 => TcProceedOnEntry::DispatcherReturn,
- proceedon => {
- return Err(ParseError::InvalidProceedOn {
- proceedon: proceedon.to_string(),
- })
+impl From for BytecodeImage {
+ fn from(value: V1BytecodeImage) -> Self {
+ // This function is mapping an empty string to None for
+ // username and password.
+ let username = if value.username.is_some() {
+ match value.username.unwrap().as_ref() {
+ "" => None,
+ u => Some(u.to_string()),
}
- })
- }
-}
-
-impl std::fmt::Display for TcProceedOnEntry {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- let v = match self {
- TcProceedOnEntry::Unspec => "unspec",
- TcProceedOnEntry::Ok => "ok",
- TcProceedOnEntry::Reclassify => "reclassify",
- TcProceedOnEntry::Shot => "shot",
- TcProceedOnEntry::Pipe => "pipe",
- TcProceedOnEntry::Stolen => "stolen",
- TcProceedOnEntry::Queued => "queued",
- TcProceedOnEntry::Repeat => "repeat",
- TcProceedOnEntry::Redirect => "redirect",
- TcProceedOnEntry::Trap => "trap",
- TcProceedOnEntry::DispatcherReturn => "dispatcher_return",
+ } else {
+ None
};
- write!(f, "{v}")
- }
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug)]
-pub struct TcProceedOn(pub(crate) Vec);
-impl Default for TcProceedOn {
- fn default() -> Self {
- TcProceedOn(vec![
- TcProceedOnEntry::Pipe,
- TcProceedOnEntry::DispatcherReturn,
- ])
- }
-}
-
-impl TcProceedOn {
- pub fn from_strings>(values: T) -> Result {
- let entries = values.as_ref();
- let mut res = vec![];
- for e in entries {
- res.push(e.to_owned().try_into()?)
- }
- Ok(TcProceedOn(res))
- }
-
- pub fn from_int32s>(values: T) -> Result {
- let entries = values.as_ref();
- if entries.is_empty() {
- return Ok(TcProceedOn::default());
- }
- let mut res = vec![];
- for e in entries {
- res.push((*e).try_into()?)
- }
- Ok(TcProceedOn(res))
- }
-
- // Valid TC return values range from -1 to 8. Since -1 is not a valid shift value,
- // 1 is added to the value to determine the bit to set in the bitmask and,
- // correspondingly, The TC dispatcher adds 1 to the return value from the BPF program
- // before it compares it to the configured bit mask.
- pub fn mask(&self) -> u32 {
- let mut proceed_on_mask: u32 = 0;
- for action in self.0.clone().into_iter() {
- proceed_on_mask |= 1 << ((action as i32) + 1);
- }
- proceed_on_mask
- }
-
- pub fn as_action_vec(&self) -> Vec {
- let mut res = vec![];
- for entry in &self.0 {
- res.push((*entry) as i32)
- }
- res
- }
-}
-
-impl std::fmt::Display for TcProceedOn {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- let res: Vec = self.0.iter().map(|x| x.to_string()).collect();
- write!(f, "{}", res.join(", "))
- }
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
-pub enum ImagePullPolicy {
- Always,
- IfNotPresent,
- Never,
-}
-
-impl std::fmt::Display for ImagePullPolicy {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- let v = match self {
- ImagePullPolicy::Always => "Always",
- ImagePullPolicy::IfNotPresent => "IfNotPresent",
- ImagePullPolicy::Never => "Never",
- };
- write!(f, "{v}")
- }
-}
-
-impl TryFrom for ImagePullPolicy {
- type Error = ParseError;
- fn try_from(value: i32) -> Result {
- Ok(match value {
- 0 => ImagePullPolicy::Always,
- 1 => ImagePullPolicy::IfNotPresent,
- 2 => ImagePullPolicy::Never,
- policy => {
- return Err(ParseError::InvalidBytecodeImagePullPolicy {
- pull_policy: policy.to_string(),
- })
+ let password = if value.password.is_some() {
+ match value.password.unwrap().as_ref() {
+ "" => None,
+ u => Some(u.to_string()),
}
- })
- }
-}
-
-impl TryFrom<&str> for ImagePullPolicy {
- type Error = ParseError;
- fn try_from(value: &str) -> Result {
- Ok(match value {
- "Always" => ImagePullPolicy::Always,
- "IfNotPresent" => ImagePullPolicy::IfNotPresent,
- "Never" => ImagePullPolicy::Never,
- policy => {
- return Err(ParseError::InvalidBytecodeImagePullPolicy {
- pull_policy: policy.to_string(),
- })
- }
- })
- }
-}
-
-impl From for i32 {
- fn from(value: ImagePullPolicy) -> Self {
- match value {
- ImagePullPolicy::Always => 0,
- ImagePullPolicy::IfNotPresent => 1,
- ImagePullPolicy::Never => 2,
- }
- }
-}
-
-impl ToString for Location {
- fn to_string(&self) -> String {
- match &self {
- // Cast imagePullPolicy into it's concrete type so we can easily print.
- Location::Image(i) => format!(
- "image: {{ url: {}, pullpolicy: {} }}",
- i.url,
- TryInto::::try_into(i.image_pull_policy).unwrap()
- ),
- Location::File(p) => format!("file: {{ path: {p} }}"),
- }
+ } else {
+ None
+ };
+ BytecodeImage::new(value.url, value.image_pull_policy, username, password)
}
}
diff --git a/bpfman-api/src/util.rs b/bpfman-api/src/util.rs
deleted file mode 100644
index cf6dcad3a..000000000
--- a/bpfman-api/src/util.rs
+++ /dev/null
@@ -1,46 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-// Copyright Authors of bpfman
-
-pub mod directories {
- // The following directories are used by bpfman. They should be created by bpfman service
- // via the bpfman.service settings. They will be manually created in the case where bpfman
- // is not being run as a service.
- //
- // ConfigurationDirectory: /etc/bpfman/
- pub const CFGDIR_MODE: u32 = 0o6750;
- pub const CFGDIR: &str = "/etc/bpfman";
- pub const CFGDIR_STATIC_PROGRAMS: &str = "/etc/bpfman/programs.d";
- pub const CFGPATH_BPFMAN_CONFIG: &str = "/etc/bpfman/bpfman.toml";
- pub const CFGPATH_CA_CERTS_PEM: &str = "/etc/bpfman/certs/ca/ca.pem";
- pub const CFGPATH_CA_CERTS_KEY: &str = "/etc/bpfman/certs/ca/ca.key";
- pub const CFGPATH_BPFMAN_CERTS_PEM: &str = "/etc/bpfman/certs/bpfman/bpfman.pem";
- pub const CFGPATH_BPFMAN_CERTS_KEY: &str = "/etc/bpfman/certs/bpfman/bpfman.key";
- pub const CFGPATH_BPFMAN_CLIENT_CERTS_PEM: &str =
- "/etc/bpfman/certs/bpfman-client/bpfman-client.pem";
- pub const CFGPATH_BPFMAN_CLIENT_CERTS_KEY: &str =
- "/etc/bpfman/certs/bpfman-client/bpfman-client.key";
-
- // RuntimeDirectory: /run/bpfman/
- pub const RTDIR_MODE: u32 = 0o6770;
- pub const RTDIR: &str = "/run/bpfman";
- pub const RTDIR_XDP_DISPATCHER: &str = "/run/bpfman/dispatchers/xdp";
- pub const RTDIR_TC_INGRESS_DISPATCHER: &str = "/run/bpfman/dispatchers/tc-ingress";
- pub const RTDIR_TC_EGRESS_DISPATCHER: &str = "/run/bpfman/dispatchers/tc-egress";
- pub const RTDIR_FS: &str = "/run/bpfman/fs";
- pub const RTDIR_FS_TC_INGRESS: &str = "/run/bpfman/fs/tc-ingress";
- pub const RTDIR_FS_TC_EGRESS: &str = "/run/bpfman/fs/tc-egress";
- pub const RTDIR_FS_XDP: &str = "/run/bpfman/fs/xdp";
- pub const RTDIR_FS_MAPS: &str = "/run/bpfman/fs/maps";
- pub const RTDIR_PROGRAMS: &str = "/run/bpfman/programs";
- pub const RTPATH_BPFMAN_SOCKET: &str = "/run/bpfman/bpfman.sock";
- // The CSI socket must be in it's own sub directory so we can easily create a dedicated
- // K8s volume mount for it.
- pub const RTDIR_BPFMAN_CSI: &str = "/run/bpfman/csi";
- pub const RTPATH_BPFMAN_CSI_SOCKET: &str = "/run/bpfman/csi/csi.sock";
- pub const RTDIR_BPFMAN_CSI_FS: &str = "/run/bpfman/csi/fs";
-
- // StateDirectory: /var/lib/bpfman/
- pub const STDIR_MODE: u32 = 0o6770;
- pub const STDIR: &str = "/var/lib/bpfman";
- pub const STDIR_BYTECODE_IMAGE_CONTENT_STORE: &str = "/var/lib/bpfman/io.bpfman.image.content";
-}
diff --git a/bpfman-ns/Cargo.toml b/bpfman-ns/Cargo.toml
new file mode 100644
index 000000000..694d88033
--- /dev/null
+++ b/bpfman-ns/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+description = "An executable to attach an eBPF program inside a container"
+edition.workspace = true
+license.workspace = true
+name = "bpfman-ns"
+repository.workspace = true
+version.workspace = true
+
+[[bin]]
+name = "bpfman-ns"
+path = "src/main.rs"
+
+[dependencies]
+anyhow = { workspace = true, features = ["std"] }
+aya = { workspace = true }
+caps = { workspace = true }
+clap = { workspace = true, features = [
+ "color",
+ "derive",
+ "help",
+ "std",
+ "suggestions",
+ "usage",
+] }
+env_logger = { workspace = true }
+log = { workspace = true }
+nix = { workspace = true, features = ["sched"] }
diff --git a/bpfman-ns/src/main.rs b/bpfman-ns/src/main.rs
new file mode 100644
index 000000000..8eeb83367
--- /dev/null
+++ b/bpfman-ns/src/main.rs
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: (MIT OR Apache-2.0)
+// Copyright Authors of bpfman
+
+use std::{fs::File, process};
+
+use anyhow::{bail, Context};
+use aya::programs::{links::FdLink, uprobe::UProbeLink, ProbeKind, UProbe};
+use clap::{Args, Parser, Subcommand};
+use log::debug;
+use nix::sched::{setns, CloneFlags};
+
+#[derive(Debug, Parser)]
+#[clap(author, version, about, long_about = None)]
+struct Cli {
+ #[clap(subcommand)]
+ command: Commands,
+}
+
+#[derive(Debug, Subcommand)]
+enum Commands {
+ /// Attach a uprobe program in the given container.
+ Uprobe(UprobeArgs),
+ // TODO: add additional commands: Kprobe, etc.
+}
+
+#[derive(Debug, Args)]
+struct UprobeArgs {
+ /// Required: path to pinned entry for bpf program on a bpffs.
+ #[clap(short, long, verbatim_doc_comment)]
+ program_pin_path: String,
+
+ /// Optional: Function to attach the uprobe to.
+ #[clap(short, long)]
+ fn_name: Option,
+
+ /// Required: Offset added to the address of the target function (or
+ /// beginning of target if no function is identified). Offsets are supported
+ /// for uretprobes, but use with caution because they can result in
+ /// unintended side effects. This should be set to zero (0) if no offset
+ /// is wanted.
+ #[clap(short, long, verbatim_doc_comment)]
+ offset: u64,
+
+ /// Required: Library name or the absolute path to a binary or library.
+ /// Example: --target "libc".
+ #[clap(short, long, verbatim_doc_comment)]
+ target: String,
+
+ /// Optional: Whether the program is a uretprobe.
+ /// [default: false]
+ #[clap(short, long, verbatim_doc_comment)]
+ retprobe: bool,
+
+ /// Optional: Only execute uprobe for given process identification number
+ /// (PID). If PID is not provided, uprobe executes for all PIDs.
+ #[clap(long, verbatim_doc_comment)]
+ pid: Option,
+
+ /// Required: Host PID of the container to attach the uprobe in.
+ #[clap(short, long)]
+ container_pid: i32,
+}
+
+fn main() -> anyhow::Result<()> {
+ env_logger::init();
+
+ has_cap(caps::CapSet::Effective, caps::Capability::CAP_BPF);
+ has_cap(caps::CapSet::Effective, caps::Capability::CAP_SYS_ADMIN);
+ has_cap(caps::CapSet::Effective, caps::Capability::CAP_SYS_CHROOT);
+
+ let bpfman_pid = process::id();
+
+ let cli = Cli::parse();
+ match cli.command {
+ Commands::Uprobe(args) => execute_uprobe_attach(args, bpfman_pid),
+ }
+}
+
+fn has_cap(cset: caps::CapSet, cap: caps::Capability) {
+ debug!("Has {}: {}", cap, caps::has_cap(None, cset, cap).unwrap());
+}
+
+fn execute_uprobe_attach(args: UprobeArgs, bpfman_pid: u32) -> anyhow::Result<()> {
+ debug!(
+ "attempting to attach uprobe in container with pid {}",
+ args.container_pid
+ );
+
+ let bpfman_mnt_file = match File::open(format!("/proc/{}/ns/mnt", bpfman_pid)) {
+ Ok(file) => file,
+ Err(e) => {
+ bail!("error opening bpfman file: {e}");
+ }
+ };
+
+ // First check if the file exists at /proc, which is where it should be
+ // when running natively on a linux host.
+ let target_mnt_file = match File::open(format!("/proc/{}/ns/mnt", args.container_pid)) {
+ Ok(file) => file,
+ // If that doesn't work, check for it in /host/proc, which is where it should
+ // be in a kubernetes deployment.
+ Err(_) => match File::open(format!("/host/proc/{}/ns/mnt", args.container_pid)) {
+ Ok(file) => file,
+ Err(e) => {
+ bail!("error opening target file: {e}");
+ }
+ },
+ };
+
+ let mut uprobe = UProbe::from_pin(args.program_pin_path.clone(), ProbeKind::UProbe)
+ .context("failed to get UProbe from pin file")?;
+
+ // Set namespace to target namespace
+ set_ns(
+ target_mnt_file,
+ CloneFlags::CLONE_NEWNS,
+ args.container_pid as u32,
+ )?;
+
+ let attach_result = uprobe.attach(args.fn_name.as_deref(), args.offset, args.target, args.pid);
+
+ let link_id = match attach_result {
+ Ok(l) => l,
+ Err(e) => {
+ bail!("error attaching uprobe: {e}");
+ }
+ };
+
+ // Set namespace back to bpfman namespace
+ set_ns(bpfman_mnt_file, CloneFlags::CLONE_NEWNS, bpfman_pid)?;
+
+ let owned_link: UProbeLink = uprobe
+ .take_link(link_id)
+ .expect("take_link failed for uprobe");
+ let fd_link: FdLink = owned_link
+ .try_into()
+ .expect("unable to get owned uprobe attach link");
+
+ fd_link.pin(format!("{}_link", args.program_pin_path))?;
+
+ Ok(())
+}
+
+fn set_ns(file: File, nstype: CloneFlags, pid: u32) -> anyhow::Result<()> {
+ let setns_result = setns(file, nstype);
+ match setns_result {
+ Ok(_) => Ok(()),
+ Err(e) => {
+ bail!(
+ "error setting ns to PID {} {:?} namespace. error: {}",
+ pid,
+ nstype,
+ e
+ );
+ }
+ }
+}
diff --git a/bpfman-operator/.gitignore b/bpfman-operator/.gitignore
index fe834879f..eef19ba50 100644
--- a/bpfman-operator/.gitignore
+++ b/bpfman-operator/.gitignore
@@ -25,3 +25,4 @@ Dockerfile.cross
*.swp
*.swo
*~
+apidocs.html
diff --git a/bpfman-operator/Containerfile.bpfman-agent b/bpfman-operator/Containerfile.bpfman-agent
index 00e26730c..209830332 100644
--- a/bpfman-operator/Containerfile.bpfman-agent
+++ b/bpfman-operator/Containerfile.bpfman-agent
@@ -1,5 +1,5 @@
# Build the manager binary
-FROM golang:1.19 as bpfman-agent-build
+FROM golang:1.21 as bpfman-agent-build
ARG TARGETOS
ARG TARGETARCH
@@ -24,14 +24,26 @@ COPY . .
WORKDIR /usr/src/bpfman/bpfman-operator
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o bpfman-agent ./cmd/bpfman-agent/main.go
-# Use distroless as minimal base image to package the manager binary
-# Refer to https://github.com/GoogleContainerTools/distroless for more details
-# Uncomment for debug build
-# FROM gcr.io/distroless/static:debug
+# Use the fedora minimal image to reduce the size of the final image but still
+# be able to easily install extra packages.
+FROM quay.io/fedora/fedora-minimal
+ARG DNF_CMD="microdnf"
-FROM gcr.io/distroless/static:nonroot
+# The full fedora image can be used for debugging purposes. To use it, comment
+# out the FROM and ARG lines above and uncomment the FROM and ARG lines below.
+# FROM fedora:38
+# ARG DNF_CMD="dnf"
+
+ARG TARGETARCH
WORKDIR /
COPY --from=bpfman-agent-build /usr/src/bpfman/bpfman-operator/bpfman-agent .
-USER 65532:65532
+
+# Install crictl
+RUN ${DNF_CMD} -y install wget tar
+ARG VERSION="v1.28.0"
+RUN wget https://github.com/kubernetes-sigs/cri-tools/releases/download/${VERSION}/crictl-${VERSION}-linux-${TARGETARCH}.tar.gz
+RUN tar zxvf crictl-${VERSION}-linux-${TARGETARCH}.tar.gz -C /usr/local/bin
+RUN rm -f crictl-${VERSION}-linux-${TARGETARCH}.tar.gz
+RUN ${DNF_CMD} -y clean all
ENTRYPOINT ["/bpfman-agent"]
diff --git a/bpfman-operator/Containerfile.bpfman-operator b/bpfman-operator/Containerfile.bpfman-operator
index 275fbc2d5..db327be54 100644
--- a/bpfman-operator/Containerfile.bpfman-operator
+++ b/bpfman-operator/Containerfile.bpfman-operator
@@ -1,5 +1,5 @@
# Build the manager binary
-FROM golang:1.19 as bpfman-operator-build
+FROM golang:1.21 as bpfman-operator-build
ARG TARGETOS
ARG TARGETARCH
@@ -24,12 +24,14 @@ COPY . .
WORKDIR /usr/src/bpfman/bpfman-operator
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o bpfman-operator ./cmd/bpfman-operator/main.go
-# Use distroless as minimal base image to package the manager binary
-# Refer to https://github.com/GoogleContainerTools/distroless for more details
-# Uncomment for debug build
-# FROM gcr.io/distroless/static:debug
+# Use the fedora minimal image to reduce the size of the final image but still
+# be able to easily install extra packages.
-FROM gcr.io/distroless/static:nonroot
+# The full fedora image can be used for debugging purposes, but you may need to
+# change "microdnf" to "dnf" below to install extra packages.
+# FROM fedora:38
+FROM quay.io/fedora/fedora-minimal
+ARG TARGETARCH
WORKDIR /
COPY --from=bpfman-operator-build /usr/src/bpfman/bpfman-operator/config/bpfman-deployment/daemonset.yaml ./config/bpfman-deployment/daemonset.yaml
COPY --from=bpfman-operator-build /usr/src/bpfman/bpfman-operator/config/bpfman-deployment/csidriverinfo.yaml ./config/bpfman-deployment/csidriverinfo.yaml
diff --git a/bpfman-operator/Makefile b/bpfman-operator/Makefile
index 519f61e62..99cdaead4 100644
--- a/bpfman-operator/Makefile
+++ b/bpfman-operator/Makefile
@@ -3,7 +3,7 @@
# To re-generate a bundle for another specific version without changing the standard setup, you can:
# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2)
# - use environment variables to overwrite this value (e.g export VERSION=0.0.2)
-VERSION ?= 0.3.1
+VERSION ?= 0.4.0
# CHANNELS define the bundle channels used in the bundle.
# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable")
@@ -181,10 +181,7 @@ ifeq ($(VERIFY_CODEGEN), true)
VERIFY_FLAG=--verify-only
endif
-OUTPUT_PKG ?= $(shell pwd)/bpfman-operator/pkg/client
-APIS_PKG ?= $(shell pwd)/bpfman-operator
-CLIENTSET_NAME ?= versioned
-CLIENTSET_PKG_NAME ?= clientset
+PKG ?= github.com/bpfman/bpfman/bpfman-operator
COMMON_FLAGS ?= ${VERIFY_FLAG} --go-header-file $(shell pwd)/hack/boilerplate.go.txt
.PHONY: manifests
@@ -199,8 +196,8 @@ generate: manifests generate-register generate-deepcopy generate-typed-clients g
.PHONY: generate-register
generate-register: register-gen ## Generate register code see all `zz_generated.register.go` files.
$(REGISTER_GEN) \
- --input-dirs "${APIS_PKG}/apis/v1alpha1" \
- --output-package "${APIS_PKG}/apis/" \
+ --input-dirs "${PKG}/apis/v1alpha1" \
+ --output-package "${PKG}/apis/" \
${COMMON_FLAGS}
.PHONY: generate-deepcopy
@@ -210,28 +207,28 @@ generate-deepcopy: ## Generate code containing DeepCopy, DeepCopyInto, and DeepC
.PHONY: generate-typed-clients
generate-typed-clients: client-gen ## Generate typed client code
$(CLIENT_GEN) \
- --clientset-name "${CLIENTSET_NAME}" \
+ --clientset-name "versioned" \
--input-base "" \
- --input "${APIS_PKG}/apis/v1alpha1" \
- --output-package "${OUTPUT_PKG}/${CLIENTSET_PKG_NAME}" \
+ --input "${PKG}/apis/v1alpha1" \
+ --output-package "${PKG}/pkg/client/clientset" \
${COMMON_FLAGS}
.PHONY: generate-typed-listers
generate-typed-listers: lister-gen ## Generate typed listers code
$(LISTER_GEN) \
- --input-dirs "${APIS_PKG}/apis/v1alpha1" \
- --output-package "${OUTPUT_PKG}/listers" \
+ --input-dirs "${PKG}/apis/v1alpha1" \
+ --output-package "${PKG}/pkg/client/listers" \
${COMMON_FLAGS}
.PHONY: generate-typed-informers
generate-typed-informers: informer-gen ## Generate typed informers code
$(INFORMER_GEN) \
- --input-dirs "${APIS_PKG}/apis/v1alpha1" \
- --versioned-clientset-package "${OUTPUT_PKG}/${CLIENTSET_PKG_NAME}/${CLIENTSET_NAME}" \
- --listers-package "${OUTPUT_PKG}/listers" \
- --output-package "${OUTPUT_PKG}/informers" \
+ --input-dirs "${PKG}/apis/v1alpha1" \
+ --versioned-clientset-package "${PKG}/pkg/client/clientset/versioned" \
+ --listers-package "${PKG}/pkg/client/listers" \
+ --output-package "${PKG}/pkg/client/informers" \
${COMMON_FLAGS}
.PHONY: fmt
@@ -332,7 +329,7 @@ catalog-push: ## Push a catalog image.
##@ CRD Deployment
-ignore-not-found ?= false
+ignore-not-found ?= true
.PHONY: install
install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
@@ -357,6 +354,8 @@ deploy: manifests kustomize ## Deploy bpfman-operator to the K8s cluster specifi
.PHONY: undeploy
undeploy: ## Undeploy bpfman-operator from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
+ kubectl delete cm bpfman-config -n bpfman
+ sleep 5 # Wait for the operator to cleanup the daemonset
$(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f -
.PHONY: kind-reload-images
@@ -378,3 +377,6 @@ deploy-openshift: manifests kustomize ## Deploy bpfman-operator to the Openshift
.PHONY: undeploy-openshift
undeploy-openshift: ## Undeploy bpfman-operator from the Openshift cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
$(KUSTOMIZE) build config/openshift | kubectl delete --ignore-not-found=$(ignore-not-found) -f -
+
+apidocs.html:
+ ./hack/api-docs/generate.sh $@
diff --git a/bpfman-operator/PROJECT b/bpfman-operator/PROJECT
index 566139902..078c4d4dc 100644
--- a/bpfman-operator/PROJECT
+++ b/bpfman-operator/PROJECT
@@ -55,4 +55,20 @@ resources:
kind: UprobeProgram
path: github.com/bpfman/bpfman/api/v1alpha1
version: v1alpha1
+- api:
+ crdVersion: v1
+ namespaced: false
+ controller: true
+ domain: bpfman.io
+ kind: FentryProgram
+ path: github.com/bpfman/bpfman/api/v1alpha1
+ version: v1alpha1
+- api:
+ crdVersion: v1
+ namespaced: false
+ controller: true
+ domain: bpfman.io
+ kind: FexitProgram
+ path: github.com/bpfman/bpfman/api/v1alpha1
+ version: v1alpha1
version: "3"
diff --git a/bpfman-operator/apis/v1alpha1/bpfprogram_types.go b/bpfman-operator/apis/v1alpha1/bpfprogram_types.go
index e0974c04f..9fa52c644 100644
--- a/bpfman-operator/apis/v1alpha1/bpfprogram_types.go
+++ b/bpfman-operator/apis/v1alpha1/bpfprogram_types.go
@@ -29,6 +29,9 @@ import (
//+kubebuilder:resource:scope=Cluster
// BpfProgram is the Schema for the Bpfprograms 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 BpfProgram struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
diff --git a/bpfman-operator/apis/v1alpha1/fentryProgram_types.go b/bpfman-operator/apis/v1alpha1/fentryProgram_types.go
new file mode 100644
index 000000000..11105f8c9
--- /dev/null
+++ b/bpfman-operator/apis/v1alpha1/fentryProgram_types.go
@@ -0,0 +1,71 @@
+/*
+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
+// +genclient:nonNamespaced
+//+kubebuilder:object:root=true
+//+kubebuilder:subresource:status
+//+kubebuilder:resource:scope=Cluster
+
+// FentryProgram is the Schema for the FentryPrograms 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
+type FentryProgram struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ Spec FentryProgramSpec `json:"spec"`
+ // +optional
+ Status FentryProgramStatus `json:"status,omitempty"`
+}
+
+// FentryProgramSpec defines the desired state of FentryProgram
+// +kubebuilder:printcolumn:name="FunctionName",type=string,JSONPath=`.spec.func_name`
+type FentryProgramSpec struct {
+ BpfProgramCommon `json:",inline"`
+
+ // Function to attach the fentry to.
+ FunctionName string `json:"func_name"`
+}
+
+// FentryProgramStatus defines the observed state of FentryProgram
+type FentryProgramStatus struct {
+ // Conditions houses the global cluster state for the FentryProgram. The explicit
+ // condition types are defined internally.
+ // +patchMergeKey=type
+ // +patchStrategy=merge
+ // +listType=map
+ // +listMapKey=type
+ Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
+}
+
+// +kubebuilder:object:root=true
+// FentryProgramList contains a list of FentryPrograms
+type FentryProgramList struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []FentryProgram `json:"items"`
+}
diff --git a/bpfman-operator/apis/v1alpha1/fexitProgram_types.go b/bpfman-operator/apis/v1alpha1/fexitProgram_types.go
new file mode 100644
index 000000000..d723f2d67
--- /dev/null
+++ b/bpfman-operator/apis/v1alpha1/fexitProgram_types.go
@@ -0,0 +1,71 @@
+/*
+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
+// +genclient:nonNamespaced
+//+kubebuilder:object:root=true
+//+kubebuilder:subresource:status
+//+kubebuilder:resource:scope=Cluster
+
+// FexitProgram is the Schema for the FexitPrograms 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
+type FexitProgram struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ Spec FexitProgramSpec `json:"spec"`
+ // +optional
+ Status FexitProgramStatus `json:"status,omitempty"`
+}
+
+// FexitProgramSpec defines the desired state of FexitProgram
+// +kubebuilder:printcolumn:name="FunctionName",type=string,JSONPath=`.spec.func_name`
+type FexitProgramSpec struct {
+ BpfProgramCommon `json:",inline"`
+
+ // Function to attach the fexit to.
+ FunctionName string `json:"func_name"`
+}
+
+// FexitProgramStatus defines the observed state of FexitProgram
+type FexitProgramStatus struct {
+ // Conditions houses the global cluster state for the FexitProgram. The explicit
+ // condition types are defined internally.
+ // +patchMergeKey=type
+ // +patchStrategy=merge
+ // +listType=map
+ // +listMapKey=type
+ Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
+}
+
+// +kubebuilder:object:root=true
+// FexitProgramList contains a list of FexitPrograms
+type FexitProgramList struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []FexitProgram `json:"items"`
+}
diff --git a/bpfman-operator/apis/v1alpha1/kprobeProgram_types.go b/bpfman-operator/apis/v1alpha1/kprobeProgram_types.go
index 500bb72ee..a72759a52 100644
--- a/bpfman-operator/apis/v1alpha1/kprobeProgram_types.go
+++ b/bpfman-operator/apis/v1alpha1/kprobeProgram_types.go
@@ -31,6 +31,7 @@ import (
// KprobeProgram is the Schema for the KprobePrograms 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="RetProbe",type=boolean,JSONPath=`.spec.retprobe`,priority=1
@@ -52,7 +53,7 @@ type KprobeProgramSpec struct {
BpfProgramCommon `json:",inline"`
// Functions to attach the kprobe to.
- FunctionNames []string `json:"func_names"`
+ FunctionName string `json:"func_name"`
// Offset added to the address of the function for kprobe.
// Not allowed for kretprobes.
@@ -65,9 +66,9 @@ type KprobeProgramSpec struct {
// +kubebuilder:default:=false
RetProbe bool `json:"retprobe"`
- // // Namespace to attach the uprobe in. (Not supported yet by bpfman.)
+ // // Host PID of container to attach the uprobe in. (Not supported yet by bpfman.)
// // +optional
- // Namespace string `json:"namespace"`
+ // ContainerPid string `json:"containerpid"`
}
// KprobeProgramStatus defines the observed state of KprobeProgram
diff --git a/bpfman-operator/apis/v1alpha1/shared_types.go b/bpfman-operator/apis/v1alpha1/shared_types.go
index ddefc597c..f92bf3e5c 100644
--- a/bpfman-operator/apis/v1alpha1/shared_types.go
+++ b/bpfman-operator/apis/v1alpha1/shared_types.go
@@ -35,6 +35,24 @@ type InterfaceSelector struct {
PrimaryNodeInterface *bool `json:"primarynodeinterface,omitempty"`
}
+// ContainerSelector identifies a set of containers. For example, this can be
+// used to identify a set of containers in which to attach uprobes.
+type ContainerSelector struct {
+ // Target namespaces.
+ // +optional
+ // +kubebuilder:default:=""
+ Namespace string `json:"namespace"`
+
+ // 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
@@ -243,6 +261,10 @@ const (
// process the bytecode selector.
BpfProgCondBytecodeSelectorError BpfProgramConditionType = "BytecodeSelectorError"
+ // BpfProgCondNoContainersOnNode indicates that there are no containers on the node
+ // that match the container selector.
+ BpfProgCondNoContainersOnNode BpfProgramConditionType = "NoContainersOnNode"
+
// None of the above conditions apply
BpfProgCondNone BpfProgramConditionType = "None"
)
@@ -311,6 +333,14 @@ func (b BpfProgramConditionType) Condition() metav1.Condition {
Message: "There was an error processing the provided bytecode selector",
}
+ case BpfProgCondNoContainersOnNode:
+ cond = metav1.Condition{
+ Type: string(BpfProgCondNoContainersOnNode),
+ Status: metav1.ConditionTrue,
+ Reason: "noContainersOnNode",
+ Message: "There are no containers on the node that match the container selector",
+ }
+
case BpfProgCondNone:
cond = metav1.Condition{
Type: string(BpfProgCondNone),
diff --git a/bpfman-operator/apis/v1alpha1/tcProgram_types.go b/bpfman-operator/apis/v1alpha1/tcProgram_types.go
index be7150d0d..37245d1c6 100644
--- a/bpfman-operator/apis/v1alpha1/tcProgram_types.go
+++ b/bpfman-operator/apis/v1alpha1/tcProgram_types.go
@@ -31,6 +31,7 @@ import (
// TcProgram is the Schema for the TcProgram 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
diff --git a/bpfman-operator/apis/v1alpha1/tracepointProgram_types.go b/bpfman-operator/apis/v1alpha1/tracepointProgram_types.go
index 6ab0c97d1..c388070bb 100644
--- a/bpfman-operator/apis/v1alpha1/tracepointProgram_types.go
+++ b/bpfman-operator/apis/v1alpha1/tracepointProgram_types.go
@@ -31,6 +31,7 @@ import (
// TracepointProgram is the Schema for the TracepointPrograms 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="TracePoint",type=string,JSONPath=`.spec.name`,priority=1
type TracepointProgram struct {
metav1.TypeMeta `json:",inline"`
diff --git a/bpfman-operator/apis/v1alpha1/uprobeProgram_types.go b/bpfman-operator/apis/v1alpha1/uprobeProgram_types.go
index 6afddb6e2..c036e6e44 100644
--- a/bpfman-operator/apis/v1alpha1/uprobeProgram_types.go
+++ b/bpfman-operator/apis/v1alpha1/uprobeProgram_types.go
@@ -31,6 +31,7 @@ import (
// UprobeProgram is the Schema for the UprobePrograms 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
@@ -64,7 +65,7 @@ type UprobeProgramSpec struct {
Offset uint64 `json:"offset"`
// Library name or the absolute path to a binary or library.
- Targets []string `json:"target"`
+ Target string `json:"target"`
// Whether the program is a uretprobe. Default is false
// +optional
@@ -76,9 +77,14 @@ type UprobeProgramSpec struct {
// +optional
Pid int32 `json:"pid"`
- // // Namespace to attach the uprobe in. (Not supported yet by bpfman.)
- // // +optional
- // Namespace string `json:"namespace"`
+ // Containers identifes the set of containers in which to attach the uprobe.
+ // If Containers is not specified, the uprobe will be attached in the
+ // bpfman-agent container. The ContainerSelector is very flexible and even
+ // allows the selection of all containers in a cluster. If an attempt is
+ // made to attach uprobes to too many containers, it can have a negative
+ // impact on on the cluster.
+ // +optional
+ Containers *ContainerSelector `json:"containers"`
}
// UprobeProgramStatus defines the observed state of UprobeProgram
diff --git a/bpfman-operator/apis/v1alpha1/xdpProgram_types.go b/bpfman-operator/apis/v1alpha1/xdpProgram_types.go
index 12b60d822..51347dca2 100644
--- a/bpfman-operator/apis/v1alpha1/xdpProgram_types.go
+++ b/bpfman-operator/apis/v1alpha1/xdpProgram_types.go
@@ -31,6 +31,7 @@ import (
// XdpProgram is the Schema for the XdpPrograms 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
diff --git a/bpfman-operator/apis/v1alpha1/zz_generated.deepcopy.go b/bpfman-operator/apis/v1alpha1/zz_generated.deepcopy.go
index 8cab3d441..13c01a44d 100644
--- a/bpfman-operator/apis/v1alpha1/zz_generated.deepcopy.go
+++ b/bpfman-operator/apis/v1alpha1/zz_generated.deepcopy.go
@@ -200,6 +200,225 @@ 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 *ContainerSelector) DeepCopyInto(out *ContainerSelector) {
+ *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 ContainerSelector.
+func (in *ContainerSelector) DeepCopy() *ContainerSelector {
+ if in == nil {
+ return nil
+ }
+ out := new(ContainerSelector)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FentryProgram) DeepCopyInto(out *FentryProgram) {
+ *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 FentryProgram.
+func (in *FentryProgram) DeepCopy() *FentryProgram {
+ if in == nil {
+ return nil
+ }
+ out := new(FentryProgram)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *FentryProgram) 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 *FentryProgramList) DeepCopyInto(out *FentryProgramList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]FentryProgram, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FentryProgramList.
+func (in *FentryProgramList) DeepCopy() *FentryProgramList {
+ if in == nil {
+ return nil
+ }
+ out := new(FentryProgramList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *FentryProgramList) 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 *FentryProgramSpec) DeepCopyInto(out *FentryProgramSpec) {
+ *out = *in
+ in.BpfProgramCommon.DeepCopyInto(&out.BpfProgramCommon)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FentryProgramSpec.
+func (in *FentryProgramSpec) DeepCopy() *FentryProgramSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(FentryProgramSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FentryProgramStatus) DeepCopyInto(out *FentryProgramStatus) {
+ *out = *in
+ if in.Conditions != nil {
+ in, out := &in.Conditions, &out.Conditions
+ *out = make([]v1.Condition, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FentryProgramStatus.
+func (in *FentryProgramStatus) DeepCopy() *FentryProgramStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(FentryProgramStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FexitProgram) DeepCopyInto(out *FexitProgram) {
+ *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 FexitProgram.
+func (in *FexitProgram) DeepCopy() *FexitProgram {
+ if in == nil {
+ return nil
+ }
+ out := new(FexitProgram)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *FexitProgram) 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 *FexitProgramList) DeepCopyInto(out *FexitProgramList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]FexitProgram, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FexitProgramList.
+func (in *FexitProgramList) DeepCopy() *FexitProgramList {
+ if in == nil {
+ return nil
+ }
+ out := new(FexitProgramList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *FexitProgramList) 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 *FexitProgramSpec) DeepCopyInto(out *FexitProgramSpec) {
+ *out = *in
+ in.BpfProgramCommon.DeepCopyInto(&out.BpfProgramCommon)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FexitProgramSpec.
+func (in *FexitProgramSpec) DeepCopy() *FexitProgramSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(FexitProgramSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FexitProgramStatus) DeepCopyInto(out *FexitProgramStatus) {
+ *out = *in
+ if in.Conditions != nil {
+ in, out := &in.Conditions, &out.Conditions
+ *out = make([]v1.Condition, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FexitProgramStatus.
+func (in *FexitProgramStatus) DeepCopy() *FexitProgramStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(FexitProgramStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ImagePullSecretSelector) DeepCopyInto(out *ImagePullSecretSelector) {
*out = *in
@@ -307,11 +526,6 @@ func (in *KprobeProgramList) DeepCopyObject() runtime.Object {
func (in *KprobeProgramSpec) DeepCopyInto(out *KprobeProgramSpec) {
*out = *in
in.BpfProgramCommon.DeepCopyInto(&out.BpfProgramCommon)
- if in.FunctionNames != nil {
- in, out := &in.FunctionNames, &out.FunctionNames
- *out = make([]string, len(*in))
- copy(*out, *in)
- }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KprobeProgramSpec.
@@ -614,10 +828,10 @@ func (in *UprobeProgramList) DeepCopyObject() runtime.Object {
func (in *UprobeProgramSpec) DeepCopyInto(out *UprobeProgramSpec) {
*out = *in
in.BpfProgramCommon.DeepCopyInto(&out.BpfProgramCommon)
- if in.Targets != nil {
- in, out := &in.Targets, &out.Targets
- *out = make([]string, len(*in))
- copy(*out, *in)
+ if in.Containers != nil {
+ in, out := &in.Containers, &out.Containers
+ *out = new(ContainerSelector)
+ (*in).DeepCopyInto(*out)
}
}
diff --git a/bpfman-operator/apis/v1alpha1/zz_generated.register.go b/bpfman-operator/apis/v1alpha1/zz_generated.register.go
index 6589989de..522ad7db9 100644
--- a/bpfman-operator/apis/v1alpha1/zz_generated.register.go
+++ b/bpfman-operator/apis/v1alpha1/zz_generated.register.go
@@ -13,6 +13,7 @@ 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 register-gen. DO NOT EDIT.
package v1alpha1
@@ -59,6 +60,10 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&BpfProgram{},
&BpfProgramList{},
+ &FentryProgram{},
+ &FentryProgramList{},
+ &FexitProgram{},
+ &FexitProgramList{},
&KprobeProgram{},
&KprobeProgramList{},
&TcProgram{},
diff --git a/bpfman-operator/cmd/bpfman-agent/main.go b/bpfman-operator/cmd/bpfman-agent/main.go
index da13b43d9..157b17dc0 100644
--- a/bpfman-operator/cmd/bpfman-agent/main.go
+++ b/bpfman-operator/cmd/bpfman-agent/main.go
@@ -59,8 +59,8 @@ func main() {
var metricsAddr string
var probeAddr string
var opts zap.Options
- flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
- flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
+ flag.StringVar(&metricsAddr, "metrics-bind-address", ":8174", "The address the metric endpoint binds to.")
+ flag.StringVar(&probeAddr, "health-probe-bind-address", ":8175", "The address the probe endpoint binds to.")
flag.Parse()
// Get the Log level for bpfman deployment where this pod is running
@@ -101,13 +101,9 @@ func main() {
os.Exit(1)
}
- // Setup bpfman Client
- configFileData := conn.LoadConfig()
- setupLog.Info("Connecting over UNIX socket to bpfman")
-
// Set up a connection to bpfman, block until bpfman is up.
- setupLog.Info("Waiting for active connection to bpfman", "endpoints", configFileData.Grpc.Endpoints)
- conn, err := conn.CreateConnection(configFileData.Grpc.Endpoints, context.Background(), insecure.NewCredentials())
+ setupLog.Info("Waiting for active connection to bpfman")
+ conn, err := conn.CreateConnection(context.Background(), insecure.NewCredentials())
if err != nil {
setupLog.Error(err, "unable to connect to bpfman")
os.Exit(1)
@@ -162,10 +158,17 @@ func main() {
os.Exit(1)
}
- if err = (&bpfmanagent.DiscoveredProgramReconciler{
+ if err = (&bpfmanagent.FentryProgramReconciler{
+ ReconcilerCommon: common,
+ }).SetupWithManager(mgr); err != nil {
+ setupLog.Error(err, "unable to create fentryProgram controller", "controller", "BpfProgram")
+ os.Exit(1)
+ }
+
+ if err = (&bpfmanagent.FexitProgramReconciler{
ReconcilerCommon: common,
}).SetupWithManager(mgr); err != nil {
- setupLog.Error(err, "unable to create discoveredProgram controller", "controller", "BpfProgram")
+ setupLog.Error(err, "unable to create fexitProgram controller", "controller", "BpfProgram")
os.Exit(1)
}
diff --git a/bpfman-operator/cmd/bpfman-operator/main.go b/bpfman-operator/cmd/bpfman-operator/main.go
index 3527791d1..829b882ba 100644
--- a/bpfman-operator/cmd/bpfman-operator/main.go
+++ b/bpfman-operator/cmd/bpfman-operator/main.go
@@ -52,8 +52,8 @@ func main() {
var enableLeaderElection bool
var probeAddr string
var opts zap.Options
- flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
- flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
+ flag.StringVar(&metricsAddr, "metrics-bind-address", ":8174", "The address the metric endpoint binds to.")
+ flag.StringVar(&probeAddr, "health-probe-bind-address", ":8175", "The address the probe endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
@@ -156,6 +156,20 @@ func main() {
os.Exit(1)
}
+ if err = (&bpfmanoperator.FentryProgramReconciler{
+ ReconcilerCommon: common,
+ }).SetupWithManager(mgr); err != nil {
+ setupLog.Error(err, "unable to create fentryProgram controller", "controller", "BpfProgram")
+ os.Exit(1)
+ }
+
+ if err = (&bpfmanoperator.FexitProgramReconciler{
+ ReconcilerCommon: common,
+ }).SetupWithManager(mgr); err != nil {
+ setupLog.Error(err, "unable to create fexitProgram controller", "controller", "BpfProgram")
+ os.Exit(1)
+ }
+
//+kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
diff --git a/bpfman-operator/config/bpfman-deployment/config.yaml b/bpfman-operator/config/bpfman-deployment/config.yaml
index 407fec3c8..ac9177f76 100644
--- a/bpfman-operator/config/bpfman-deployment/config.yaml
+++ b/bpfman-operator/config/bpfman-deployment/config.yaml
@@ -8,12 +8,13 @@ data:
bpfman.agent.image: quay.io/bpfman/bpfman-agent:latest
bpfman.image: quay.io/bpfman/bpfman:latest
## Can be set to "info", "debug", or "trace"
- bpfman.agent.log.level: "info"
+ bpfman.agent.log.level: info
## See https://docs.rs/env_logger/latest/env_logger/ for configuration options
- bpfman.log.level: "info"
- ## Must be configured at startup
+ bpfman.log.level: info
+ bpfman.agent.healthprobe.addr: :8175
+ bpfman.agent.metric.addr: 127.0.0.1:8174
+ # Wait 5 minutes since cosign is slow, https://github.com/bpfman/bpfman/issues/1043
bpfman.toml: |
- [[grpc.endpoints]]
- type = "unix"
- path = "/bpfman-sock/bpfman.sock"
- enabled = true
+ [database]
+ max_retries = 30
+ millisec_delay = 10000
diff --git a/bpfman-operator/config/bpfman-deployment/daemonset.yaml b/bpfman-operator/config/bpfman-deployment/daemonset.yaml
index 0a2574b10..9653adc49 100644
--- a/bpfman-operator/config/bpfman-deployment/daemonset.yaml
+++ b/bpfman-operator/config/bpfman-deployment/daemonset.yaml
@@ -75,14 +75,10 @@ spec:
fieldPath: spec.nodeName
volumeMounts:
- name: bpfman-sock
- mountPath: /bpfman-sock
+ mountPath: /run/bpfman-sock
- name: runtime
mountPath: /run/bpfman
mountPropagation: Bidirectional
- - name: bpfman-config
- mountPath: /etc/bpfman/bpfman.toml
- subPath: bpfman.toml
- readOnly: true
# This mount is needed to attach tracepoint programs
- name: host-debug
mountPath: /sys/kernel/debug
@@ -101,9 +97,21 @@ spec:
name: mountpoint-dir
- mountPath: /tmp
name: tmp-dir
+ # Used to attach programs inside of containers
+ - mountPath: /host/proc
+ name: host-proc
+ - name: bpfman-config
+ mountPath: /etc/bpfman/bpfman.toml
+ subPath: bpfman.toml
+ readOnly: true
- name: bpfman-agent
command: [/bpfman-agent]
+ args:
+ - --health-probe-bind-address=:8175
+ - --metrics-bind-address=127.0.0.1:8174
image: quay.io/bpfman/bpfman-agent:latest
+ securityContext:
+ privileged: true
imagePullPolicy: IfNotPresent
env:
- name: KUBE_NODE_NAME
@@ -117,11 +125,19 @@ spec:
key: bpfman.agent.log.level
volumeMounts:
- name: bpfman-sock
- mountPath: /bpfman-sock
- - name: bpfman-config
- mountPath: /etc/bpfman/bpfman.toml
- subPath: bpfman.toml
- readOnly: true
+ mountPath: /run/bpfman-sock
+ ## The following five mounts are used by crictl for attaching
+ ## uprobes in user containers
+ - mountPath: /run/containerd/containerd.sock
+ name: host-containerd
+ - mountPath: /run/crio/crio.sock
+ name: host-crio
+ - mountPath: /var/run/dockershim.sock
+ name: host-dockershim
+ - mountPath: /var/run/cri-dockerd.sock
+ name: host-dockerd
+ - mountPath: /etc/crictl.yaml
+ name: host-crictl-config
- name: node-driver-registrar
image: quay.io/bpfman/csi-node-driver-registrar:v2.9.0
imagePullPolicy: IfNotPresent
@@ -190,3 +206,21 @@ spec:
path: /tmp
type: DirectoryOrCreate
name: tmp-dir
+ - hostPath:
+ path: /proc
+ name: host-proc
+ - hostPath:
+ path: /run/containerd/containerd.sock
+ name: host-containerd
+ - hostPath:
+ path: /run/crio/crio.sock
+ name: host-crio
+ - hostPath:
+ path: /var/run/dockershim.sock
+ name: host-dockershim
+ - hostPath:
+ path: /var/run/cri-dockerd.sock
+ name: host-dockerd
+ - hostPath:
+ path: /etc/crictl.yaml
+ name: host-crictl-config
diff --git a/bpfman-operator/config/bpfman-operator-deployment/deployment.yaml b/bpfman-operator/config/bpfman-operator-deployment/deployment.yaml
index a3e7e7ae0..a8dbc0100 100644
--- a/bpfman-operator/config/bpfman-operator-deployment/deployment.yaml
+++ b/bpfman-operator/config/bpfman-operator-deployment/deployment.yaml
@@ -74,23 +74,23 @@ spec:
imagePullPolicy: IfNotPresent
env:
- name: GO_LOG
- value: "debug"
+ value: debug
name: bpfman-operator
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- - "ALL"
+ - ALL
livenessProbe:
httpGet:
path: /healthz
- port: 8081
+ port: 8175
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /readyz
- port: 8081
+ port: 8175
initialDelaySeconds: 5
periodSeconds: 10
# TODO(user): Configure the resources accordingly based on the project requirements.
diff --git a/bpfman-operator/config/crd/bases/bpfman.io_bpfprograms.yaml b/bpfman-operator/config/crd/bases/bpfman.io_bpfprograms.yaml
index d93d3bb65..2d930330a 100644
--- a/bpfman-operator/config/crd/bases/bpfman.io_bpfprograms.yaml
+++ b/bpfman-operator/config/crd/bases/bpfman.io_bpfprograms.yaml
@@ -15,7 +15,17 @@ spec:
singular: bpfprogram
scope: Cluster
versions:
- - name: v1alpha1
+ - 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: BpfProgram is the Schema for the Bpfprograms API
diff --git a/bpfman-operator/config/crd/bases/bpfman.io_fentryprograms.yaml b/bpfman-operator/config/crd/bases/bpfman.io_fentryprograms.yaml
new file mode 100644
index 000000000..34d6d9482
--- /dev/null
+++ b/bpfman-operator/config/crd/bases/bpfman.io_fentryprograms.yaml
@@ -0,0 +1,298 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.11.3
+ creationTimestamp: null
+ name: fentryprograms.bpfman.io
+spec:
+ group: bpfman.io
+ names:
+ kind: FentryProgram
+ listKind: FentryProgramList
+ plural: fentryprograms
+ singular: fentryprogram
+ scope: Cluster
+ 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
+ name: v1alpha1
+ schema:
+ openAPIV3Schema:
+ description: FentryProgram is the Schema for the FentryPrograms 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: FentryProgramSpec defines the desired state of FentryProgram
+ 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
+ func_name:
+ description: Function to attach the fentry to.
+ type: string
+ globaldata:
+ additionalProperties:
+ format: byte
+ type: string
+ description: GlobalData allows the user to 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
+ required:
+ - key
+ - operator
+ type: object
+ type: array
+ 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
+ required:
+ - key
+ - operator
+ type: object
+ type: array
+ matchLabels:
+ additionalProperties:
+ type: string
+ description: matchLabels is a map of {key,value} pairs. A single
+ {key,value} in the matchLabels map is equivalent to an element
+ of matchExpressions, whose key field is "key", the operator
+ is "In", and the values array contains only "value". The requirements
+ are ANDed.
+ type: object
+ type: object
+ x-kubernetes-map-type: atomic
+ required:
+ - bpffunctionname
+ - bytecode
+ - func_name
+ - nodeselector
+ type: object
+ status:
+ description: FentryProgramStatus defines the observed state of FentryProgram
+ properties:
+ conditions:
+ description: Conditions houses the global cluster state for the FentryProgram.
+ The explicit condition types are defined internally.
+ items:
+ description: "Condition contains details for one aspect of the current
+ state of this API Resource. --- This struct is intended for direct
+ use as an array at the field path .status.conditions. For example,
+ \n type FooStatus struct{ // Represents the observations of a
+ foo's current state. // Known .status.conditions.type are: \"Available\",
+ \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
+ // +listType=map // +listMapKey=type Conditions []metav1.Condition
+ `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
+ protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
+ 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/bpfman-operator/config/crd/bases/bpfman.io_fexitprograms.yaml b/bpfman-operator/config/crd/bases/bpfman.io_fexitprograms.yaml
new file mode 100644
index 000000000..23c8ef208
--- /dev/null
+++ b/bpfman-operator/config/crd/bases/bpfman.io_fexitprograms.yaml
@@ -0,0 +1,298 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.11.3
+ creationTimestamp: null
+ name: fexitprograms.bpfman.io
+spec:
+ group: bpfman.io
+ names:
+ kind: FexitProgram
+ listKind: FexitProgramList
+ plural: fexitprograms
+ singular: fexitprogram
+ scope: Cluster
+ 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
+ name: v1alpha1
+ schema:
+ openAPIV3Schema:
+ description: FexitProgram is the Schema for the FexitPrograms 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: FexitProgramSpec defines the desired state of FexitProgram
+ 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
+ func_name:
+ description: Function to attach the fexit to.
+ type: string
+ globaldata:
+ additionalProperties:
+ format: byte
+ type: string
+ description: GlobalData allows the user to 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
+ required:
+ - key
+ - operator
+ type: object
+ type: array
+ 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
+ required:
+ - key
+ - operator
+ type: object
+ type: array
+ matchLabels:
+ additionalProperties:
+ type: string
+ description: matchLabels is a map of {key,value} pairs. A single
+ {key,value} in the matchLabels map is equivalent to an element
+ of matchExpressions, whose key field is "key", the operator
+ is "In", and the values array contains only "value". The requirements
+ are ANDed.
+ type: object
+ type: object
+ x-kubernetes-map-type: atomic
+ required:
+ - bpffunctionname
+ - bytecode
+ - func_name
+ - nodeselector
+ type: object
+ status:
+ description: FexitProgramStatus defines the observed state of FexitProgram
+ properties:
+ conditions:
+ description: Conditions houses the global cluster state for the FexitProgram.
+ The explicit condition types are defined internally.
+ items:
+ description: "Condition contains details for one aspect of the current
+ state of this API Resource. --- This struct is intended for direct
+ use as an array at the field path .status.conditions. For example,
+ \n type FooStatus struct{ // Represents the observations of a
+ foo's current state. // Known .status.conditions.type are: \"Available\",
+ \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
+ // +listType=map // +listMapKey=type Conditions []metav1.Condition
+ `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
+ protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
+ 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/bpfman-operator/config/crd/bases/bpfman.io_kprobeprograms.yaml b/bpfman-operator/config/crd/bases/bpfman.io_kprobeprograms.yaml
index 5ac7ee8f8..550480866 100644
--- a/bpfman-operator/config/crd/bases/bpfman.io_kprobeprograms.yaml
+++ b/bpfman-operator/config/crd/bases/bpfman.io_kprobeprograms.yaml
@@ -22,6 +22,9 @@ spec:
- jsonPath: .spec.nodeselector
name: NodeSelector
type: string
+ - jsonPath: .status.conditions[0].reason
+ name: Status
+ type: string
- jsonPath: .spec.func_name
name: FunctionName
priority: 1
@@ -101,11 +104,9 @@ spec:
description: Path is used to specify a bytecode object via filepath.
type: string
type: object
- func_names:
+ func_name:
description: Functions to attach the kprobe to.
- items:
- type: string
- type: array
+ type: string
globaldata:
additionalProperties:
format: byte
@@ -226,7 +227,7 @@ spec:
required:
- bpffunctionname
- bytecode
- - func_names
+ - func_name
- nodeselector
type: object
x-kubernetes-validations:
diff --git a/bpfman-operator/config/crd/bases/bpfman.io_tcprograms.yaml b/bpfman-operator/config/crd/bases/bpfman.io_tcprograms.yaml
index a6803ea06..95731e9ea 100644
--- a/bpfman-operator/config/crd/bases/bpfman.io_tcprograms.yaml
+++ b/bpfman-operator/config/crd/bases/bpfman.io_tcprograms.yaml
@@ -22,6 +22,9 @@ spec:
- jsonPath: .spec.nodeselector
name: NodeSelector
type: string
+ - jsonPath: .status.conditions[0].reason
+ name: Status
+ type: string
- jsonPath: .spec.priority
name: Priority
priority: 1
diff --git a/bpfman-operator/config/crd/bases/bpfman.io_tracepointprograms.yaml b/bpfman-operator/config/crd/bases/bpfman.io_tracepointprograms.yaml
index f1d159132..d1d1b5392 100644
--- a/bpfman-operator/config/crd/bases/bpfman.io_tracepointprograms.yaml
+++ b/bpfman-operator/config/crd/bases/bpfman.io_tracepointprograms.yaml
@@ -22,6 +22,9 @@ spec:
- jsonPath: .spec.nodeselector
name: NodeSelector
type: string
+ - jsonPath: .status.conditions[0].reason
+ name: Status
+ type: string
- jsonPath: .spec.name
name: TracePoint
priority: 1
diff --git a/bpfman-operator/config/crd/bases/bpfman.io_uprobeprograms.yaml b/bpfman-operator/config/crd/bases/bpfman.io_uprobeprograms.yaml
index 2eea1d820..409bca4ad 100644
--- a/bpfman-operator/config/crd/bases/bpfman.io_uprobeprograms.yaml
+++ b/bpfman-operator/config/crd/bases/bpfman.io_uprobeprograms.yaml
@@ -22,6 +22,9 @@ spec:
- jsonPath: .spec.nodeselector
name: NodeSelector
type: string
+ - jsonPath: .status.conditions[0].reason
+ name: Status
+ type: string
- jsonPath: .spec.func_name
name: FunctionName
priority: 1
@@ -109,6 +112,74 @@ spec:
description: Path is used to specify a bytecode object via filepath.
type: string
type: object
+ containers:
+ description: Containers identifes the set of containers in which to
+ attach the uprobe. If Containers is not specified, the uprobe will
+ be attached in the bpfman-agent container. The ContainerSelector
+ is very flexible and even allows the selection of all containers
+ in a cluster. If an attempt is made to attach uprobes to too many
+ containers, it can have a negative impact on on the cluster.
+ properties:
+ containernames:
+ description: Name(s) of container(s). If none are specified,
+ all containers in the pod are selected.
+ items:
+ type: string
+ type: array
+ namespace:
+ default: ""
+ description: Target namespaces.
+ type: string
+ pods:
+ description: Target pods. This field must be specified, to select
+ all pods use standard metav1.LabelSelector semantics and make
+ it empty.
+ properties:
+ matchExpressions:
+ description: matchExpressions is a list of label selector
+ requirements. The requirements are ANDed.
+ items:
+ description: A label selector requirement is a selector
+ that contains values, a key, and an operator that relates
+ the key and values.
+ properties:
+ key:
+ description: key is the label key that the selector
+ applies to.
+ type: string
+ operator:
+ description: operator represents a key's relationship
+ to a set of values. Valid operators are In, NotIn,
+ Exists and DoesNotExist.
+ type: string
+ values:
+ description: values is an array of string values. If
+ the operator is In or NotIn, the values array must
+ be non-empty. If the operator is Exists or DoesNotExist,
+ the values array must be empty. This array is replaced
+ during a strategic merge patch.
+ items:
+ type: string
+ type: array
+ required:
+ - key
+ - operator
+ type: object
+ type: array
+ 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
@@ -235,9 +306,7 @@ spec:
type: boolean
target:
description: Library name or the absolute path to a binary or library.
- items:
- type: string
- type: array
+ type: string
required:
- bpffunctionname
- bytecode
diff --git a/bpfman-operator/config/crd/bases/bpfman.io_xdpprograms.yaml b/bpfman-operator/config/crd/bases/bpfman.io_xdpprograms.yaml
index 4e9baa431..920dd3338 100644
--- a/bpfman-operator/config/crd/bases/bpfman.io_xdpprograms.yaml
+++ b/bpfman-operator/config/crd/bases/bpfman.io_xdpprograms.yaml
@@ -22,6 +22,9 @@ spec:
- jsonPath: .spec.nodeselector
name: NodeSelector
type: string
+ - jsonPath: .status.conditions[0].reason
+ name: Status
+ type: string
- jsonPath: .spec.priority
name: Priority
priority: 1
diff --git a/bpfman-operator/config/crd/kustomization.yaml b/bpfman-operator/config/crd/kustomization.yaml
index 4d26488ac..ba17a6bcc 100644
--- a/bpfman-operator/config/crd/kustomization.yaml
+++ b/bpfman-operator/config/crd/kustomization.yaml
@@ -8,6 +8,8 @@ resources:
- bases/bpfman.io_xdpprograms.yaml
- bases/bpfman.io_kprobeprograms.yaml
- bases/bpfman.io_uprobeprograms.yaml
+ - bases/bpfman.io_fentryprograms.yaml
+ - bases/bpfman.io_fexitprograms.yaml
#+kubebuilder:scaffold:crdkustomizeresource
@@ -20,6 +22,8 @@ patchesStrategicMerge:
#- patches/webhook_in_tracepointprograms.yaml
#- patches/webhook_in_kprobeprograms.yaml
#- patches/webhook_in_uprobeprograms.yaml
+#- patches/webhook_in_fentryprograms.yaml
+#- patches/webhook_in_fexitprograms.yaml
#+kubebuilder:scaffold:crdkustomizewebhookpatch
# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
@@ -30,6 +34,8 @@ patchesStrategicMerge:
#- patches/cainjection_in_tracepointprograms.yaml
#- patches/cainjection_in_kprobeprograms.yaml
#- patches/cainjection_in_uprobeprograms.yaml
+#- patches/cainjection_in_fentryprograms.yaml
+#- patches/cainjection_in_fentryprograms.yaml
#+kubebuilder:scaffold:crdkustomizecainjectionpatch
# the following config is for teaching kustomize how to do kustomization for CRDs.
diff --git a/bpfman-operator/config/crd/patches/cainjection_in_fentryprograms.yaml b/bpfman-operator/config/crd/patches/cainjection_in_fentryprograms.yaml
new file mode 100644
index 000000000..c171146d6
--- /dev/null
+++ b/bpfman-operator/config/crd/patches/cainjection_in_fentryprograms.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: fentryprograms.bpfman.io
diff --git a/bpfman-operator/config/crd/patches/cainjection_in_fexitprograms.yaml b/bpfman-operator/config/crd/patches/cainjection_in_fexitprograms.yaml
new file mode 100644
index 000000000..b06844e6e
--- /dev/null
+++ b/bpfman-operator/config/crd/patches/cainjection_in_fexitprograms.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: fexitprograms.bpfman.io
diff --git a/bpfman-operator/config/crd/patches/webhook_in_fentryprograms.yaml b/bpfman-operator/config/crd/patches/webhook_in_fentryprograms.yaml
new file mode 100644
index 000000000..39e7827a5
--- /dev/null
+++ b/bpfman-operator/config/crd/patches/webhook_in_fentryprograms.yaml
@@ -0,0 +1,16 @@
+# The following patch enables a conversion webhook for the CRD
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ name: fentryprograms.bpfman.io
+spec:
+ conversion:
+ strategy: Webhook
+ webhook:
+ clientConfig:
+ service:
+ namespace: system
+ name: webhook-service
+ path: /convert
+ conversionReviewVersions:
+ - v1
diff --git a/bpfman-operator/config/crd/patches/webhook_in_fexitprograms.yaml b/bpfman-operator/config/crd/patches/webhook_in_fexitprograms.yaml
new file mode 100644
index 000000000..9b09fc690
--- /dev/null
+++ b/bpfman-operator/config/crd/patches/webhook_in_fexitprograms.yaml
@@ -0,0 +1,16 @@
+# The following patch enables a conversion webhook for the CRD
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ name: fexitprograms.bpfman.io
+spec:
+ conversion:
+ strategy: Webhook
+ webhook:
+ clientConfig:
+ service:
+ namespace: system
+ name: webhook-service
+ path: /convert
+ conversionReviewVersions:
+ - v1
diff --git a/bpfman-operator/config/default/manager_auth_proxy_patch.yaml b/bpfman-operator/config/default/manager_auth_proxy_patch.yaml
index 61acfd03f..db945cf75 100644
--- a/bpfman-operator/config/default/manager_auth_proxy_patch.yaml
+++ b/bpfman-operator/config/default/manager_auth_proxy_patch.yaml
@@ -30,13 +30,13 @@ spec:
allowPrivilegeEscalation: false
capabilities:
drop:
- - "ALL"
+ - ALL
image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.0
args:
- - "--secure-listen-address=0.0.0.0:8443"
- - "--upstream=http://127.0.0.1:8080/"
- - "--logtostderr=true"
- - "--v=0"
+ - --secure-listen-address=0.0.0.0:8443
+ - --upstream=http://127.0.0.1:8174/
+ - --logtostderr=true
+ - --v=0
ports:
- containerPort: 8443
protocol: TCP
@@ -50,6 +50,6 @@ spec:
memory: 64Mi
- name: bpfman-operator
args:
- - "--health-probe-bind-address=:8081"
- - "--metrics-bind-address=127.0.0.1:8080"
- - "--leader-elect"
+ - --health-probe-bind-address=:8175
+ - --metrics-bind-address=127.0.0.1:8174
+ - --leader-elect
diff --git a/bpfman-operator/config/manifests/bases/bpfman-operator.clusterserviceversion.yaml b/bpfman-operator/config/manifests/bases/bpfman-operator.clusterserviceversion.yaml
index 76bc2de64..a71c2f4af 100644
--- a/bpfman-operator/config/manifests/bases/bpfman-operator.clusterserviceversion.yaml
+++ b/bpfman-operator/config/manifests/bases/bpfman-operator.clusterserviceversion.yaml
@@ -42,6 +42,16 @@ spec:
kind: UprobeProgram
name: uprobeprograms.bpfman.io
version: v1alpha1
+ - description: FentryProgram is the Schema for the Fentryprograms API
+ displayName: Fentry Program
+ kind: FentryProgram
+ name: fentryprograms.bpfman.io
+ version: v1alpha1
+ - description: FexitProgram is the Schema for the Fexitprograms API
+ displayName: Fexit Program
+ kind: FexitProgram
+ name: fexitprograms.bpfman.io
+ version: v1alpha1
description:
"The bpfman Operator is a Kubernetes Operator for deploying [bpfman](https://bpfman.netlify.app/),
a system daemon\nfor managing eBPF programs. It deploys bpfman itself along with
@@ -55,14 +65,310 @@ spec:
`bpfman.agent.image`: The image used for the bpfman-agent, defaults to `quay.io/bpfman/bpfman-agent:latest`\n-
`bpfman.image`: The image used for bpfman, defaults to `quay.io/bpfman/bpfman:latest`\n-
`bpfman.log.level`: the log level for bpfman, currently 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-`bpfman.toml`:
- bpfman's custom configuration file.\n\n## Deploying eBPF Programs\n\nThe bpfman operator
- deploys eBPF programs via CRDs. The following CRDs are currently avaliable, \n\n-
- XdpProgram\n- TcProgram\n- TracepointProgram\n- KprobeProgram\n- UprobeProgram\n\n## More information\n\nPlease
+ `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-
+ 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."
displayName: Bpfman Operator
icon:
- - base64data: PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB3aWR0aD0iNTEyIgogICBoZWlnaHQ9IjEyOCIKICAgdmlld0JveD0iMCAwIDUxMiAxMjgiCiAgIHZlcnNpb249IjEuMSIKICAgaWQ9InN2ZzUiCiAgIGlua3NjYXBlOnZlcnNpb249IjEuMiAoZGMyYWVkYWYwMywgMjAyMi0wNS0xNSkiCiAgIHNvZGlwb2RpOmRvY25hbWU9ImJwZmQuc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBpZD0ibmFtZWR2aWV3NyIKICAgICBwYWdlY29sb3I9IiM1MDUwNTAiCiAgICAgYm9yZGVyY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyb3BhY2l0eT0iMSIKICAgICBpbmtzY2FwZTpzaG93cGFnZXNoYWRvdz0iMCIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMCIKICAgICBpbmtzY2FwZTpwYWdlY2hlY2tlcmJvYXJkPSIxIgogICAgIGlua3NjYXBlOmRlc2tjb2xvcj0iIzUwNTA1MCIKICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiCiAgICAgc2hvd2dyaWQ9ImZhbHNlIgogICAgIGlua3NjYXBlOnpvb209IjEuNTM1IgogICAgIGlua3NjYXBlOmN4PSIxNjkuNzA2ODQiCiAgICAgaW5rc2NhcGU6Y3k9Ii01Mi40NDI5OTciCiAgICAgaW5rc2NhcGU6d2luZG93LXdpZHRoPSIxOTIwIgogICAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9IjEwMjYiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjAiCiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjAiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiIC8+CiAgPGRlZnMKICAgICBpZD0iZGVmczIiPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDY0IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3o9IjUxMiA6IDY0IDogMSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIyNTYgOiA0Mi42NjY2NjcgOiAxIgogICAgICAgaWQ9InBlcnNwZWN0aXZlMTc5MjkiIC8+CiAgICA8Y2xpcFBhdGgKICAgICAgIGNsaXBQYXRoVW5pdHM9InVzZXJTcGFjZU9uVXNlIgogICAgICAgaWQ9ImNsaXBQYXRoMjcyNjAiPgogICAgICA8cmVjdAogICAgICAgICBzdHlsZT0iZmlsbDpub25lO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDo0LjM2MjgxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIgogICAgICAgICBpZD0icmVjdDI3MjYyIgogICAgICAgICB3aWR0aD0iMTA3LjE1MTg0IgogICAgICAgICBoZWlnaHQ9IjEwNy4xNTE4NCIKICAgICAgICAgeD0iMjMuMTAzNDE2IgogICAgICAgICB5PSIzMS41MzM0MjgiCiAgICAgICAgIHJ5PSIyMC4xNzk3OSIgLz4KICAgIDwvY2xpcFBhdGg+CiAgPC9kZWZzPgogIDxnCiAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiCiAgICAgaW5rc2NhcGU6Z3JvdXBtb2RlPSJsYXllciIKICAgICBpZD0ibGF5ZXIxIj4KICAgIDxnCiAgICAgICBpZD0iZzI3MzA0IgogICAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMS4wNzAxMDM3LDAsMCwxLjA3MDEwMzcsMTkuMTA3MDU0LC04LjY5MjY1NzcpIj4KICAgICAgPGcKICAgICAgICAgaWQ9ImcyNzI1OCIKICAgICAgICAgY2xpcC1wYXRoPSJ1cmwoI2NsaXBQYXRoMjcyNjApIgogICAgICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtOCwtMTgpIj4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJlbGxpcHNlMTU2ODAiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2YzYjYyNjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC4yOTk4NTI7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgdHJhbnNmb3JtPSJzY2FsZSgtMSkiCiAgICAgICAgICAgZD0ibSAtNjYuMDgzNjQsMjQuMTYzNTc4IGEgMTIuMzcyNTI5LDMuOTcyMjMyOCAwIDAgMSAtMTIuMzcyNTI5LDMuOTcyMjMzIDEyLjM3MjUyOSwzLjk3MjIzMjggMCAwIDEgLTEyLjM3MjUyOSwtMy45NzIyMzMgMTIuMzcyNTI5LDMuOTcyMjMyOCAwIDAgMSAxMi4zNzI1MjksLTMuOTcyMjMzIDEyLjM3MjUyOSwzLjk3MjIzMjggMCAwIDEgMTIuMzcyNTI5LDMuOTcyMjMzIHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0iZWxsaXBzZTE1Njc4IgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNmM2I2MjY7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuNDg5NDA3O3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIHRyYW5zZm9ybT0ic2NhbGUoLTEpIgogICAgICAgICAgIGQ9Im0gLTU4LjI2MjIyNCwzMC4xNjM1NzggYSAyMC4xOTM5NDUsNi40ODMzMTg4IDAgMCAxIC0yMC4xOTM5NDUsNi40ODMzMTkgMjAuMTkzOTQ1LDYuNDgzMzE4OCAwIDAgMSAtMjAuMTkzOTQ1LC02LjQ4MzMxOSAyMC4xOTM5NDUsNi40ODMzMTg4IDAgMCAxIDIwLjE5Mzk0NSwtNi40ODMzMTkgMjAuMTkzOTQ1LDYuNDgzMzE4OCAwIDAgMSAyMC4xOTM5NDUsNi40ODMzMTkgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJlbGxpcHNlMTU2NzYiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2YzYjYyNjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC43O3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIHRyYW5zZm9ybT0ic2NhbGUoLTEpIgogICAgICAgICAgIGQ9Im0gLTQ5LjU3MjczMywzOC4xNjM1NzggYSAyOC44ODM0MzYsOS4yNzMxMDI4IDAgMCAxIC0yOC44ODM0MzYsOS4yNzMxMDMgMjguODgzNDM2LDkuMjczMTAyOCAwIDAgMSAtMjguODgzNDQxLC05LjI3MzEwMyAyOC44ODM0MzYsOS4yNzMxMDI4IDAgMCAxIDI4Ljg4MzQ0MSwtOS4yNzMxMDMgMjguODgzNDM2LDkuMjczMTAyOCAwIDAgMSAyOC44ODM0MzYsOS4yNzMxMDMgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJlbGxpcHNlMTU2NzQiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2YzYjYyNjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC43O3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIHRyYW5zZm9ybT0ic2NhbGUoLTEpIgogICAgICAgICAgIGQ9Ik0gLTQwLjEyNjQ4LDQ4LjE2MzU4NiBBIDM4LjMyOTY4OSwxMi4zMDU4NDcgMCAwIDEgLTc4LjQ1NjE2OSw2MC40Njk0MzMgMzguMzI5Njg5LDEyLjMwNTg0NyAwIDAgMSAtMTE2Ljc4NTg2LDQ4LjE2MzU4NiAzOC4zMjk2ODksMTIuMzA1ODQ3IDAgMCAxIC03OC40NTYxNjksMzUuODU3NzM4IDM4LjMyOTY4OSwxMi4zMDU4NDcgMCAwIDEgLTQwLjEyNjQ4LDQ4LjE2MzU4NiBaIiAvPgogICAgICAgIDxwYXRoCiAgICAgICAgICAgaWQ9ImVsbGlwc2UxNTY3MiIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZjNiNjI2O3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDowLjc7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgdHJhbnNmb3JtPSJzY2FsZSgtMSkiCiAgICAgICAgICAgZD0iTSAtMzQuOTE0NzUzLDU4LjE2MzU4NiBBIDQzLjU0MTQxNiwxMy45NzkwODYgMCAwIDEgLTc4LjQ1NjE2OSw3Mi4xNDI2NzIgNDMuNTQxNDE2LDEzLjk3OTA4NiAwIDAgMSAtMTIxLjk5NzU5LDU4LjE2MzU4NiA0My41NDE0MTYsMTMuOTc5MDg2IDAgMCAxIC03OC40NTYxNjksNDQuMTg0NSA0My41NDE0MTYsMTMuOTc5MDg2IDAgMCAxIC0zNC45MTQ3NTMsNTguMTYzNTg2IFoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0icmVjdDE1NjcwIgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNmM2I2MjY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuNjI1MDE4O3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Im0gNDEuNTY4NTc0LC03MS43MDQ1MzEgaCA3My44NTU1MDYgYyAzLjY5ODc5LDAgNi42NzY1MSwyLjk3NzcyNyA2LjY3NjUxLDYuNjc2NTE5IHYgOS4xMTYwODIgYyAtMTAuMTY0MDQsMjEuMDE4ODc1IC04NC43OTIyMzksMTQuOTc1Nzk5IC04Ny4yMDg1MzQsMCB2IC05LjExNjA4MiBjIDAsLTMuNjk4NzkyIDIuOTc3NzI3LC02LjY3NjUxOSA2LjY3NjUxOCwtNi42NzY1MTkgeiIKICAgICAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9InNzc2Njc3MiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0iZWxsaXBzZTE0ODIyIgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNmM2I2MjY7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuNztzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MS40MTQyMSIKICAgICAgICAgICBkPSJNIDEyMS45OTc1OSwtNzQuMzcwNTE0IEEgNDMuNTQxNDE2LDEzLjk3OTA4NiAwIDAgMSA3OC40NTYxNjksLTYwLjM5MTQyOCA0My41NDE0MTYsMTMuOTc5MDg2IDAgMCAxIDM0LjkxNDc1MywtNzQuMzcwNTE0IDQzLjU0MTQxNiwxMy45NzkwODYgMCAwIDEgNzguNDU2MTY5LC04OC4zNDk2IDQzLjU0MTQxNiwxMy45NzkwODYgMCAwIDEgMTIxLjk5NzU5LC03NC4zNzA1MTQgWiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJlbGxpcHNlMTQ4MjAiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2YzYjYyNjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC43O3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gMTE2Ljc4NTg2LC04NC4zNzA1MTQgQSAzOC4zMjk2ODksMTIuMzA1ODQ3IDAgMCAxIDc4LjQ1NjE2OSwtNzIuMDY0NjY3IDM4LjMyOTY4OSwxMi4zMDU4NDcgMCAwIDEgNDAuMTI2NDgsLTg0LjM3MDUxNCAzOC4zMjk2ODksMTIuMzA1ODQ3IDAgMCAxIDc4LjQ1NjE2OSwtOTYuNjc2MzYxIDM4LjMyOTY4OSwxMi4zMDU4NDcgMCAwIDEgMTE2Ljc4NTg2LC04NC4zNzA1MTQgWiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJwYXRoMTQ4MTgiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2YzYjYyNjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC43O3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Im0gMTA3LjMzOTYxLC05NC4zNzA1MTQgYSAyOC44ODM0MzYsOS4yNzMxMDI4IDAgMCAxIC0yOC44ODM0NDEsOS4yNzMxMDMgMjguODgzNDM2LDkuMjczMTAyOCAwIDAgMSAtMjguODgzNDM2LC05LjI3MzEwMyAyOC44ODM0MzYsOS4yNzMxMDI4IDAgMCAxIDI4Ljg4MzQzNiwtOS4yNzMxMDYgMjguODgzNDM2LDkuMjczMTAyOCAwIDAgMSAyOC44ODM0NDEsOS4yNzMxMDYgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJlbGxpcHNlMTQ4ODgiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2YzYjYyNjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC40ODk0MDc7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgZD0ibSA5OC42NTAxMTQsLTEwMi4zNzA1MSBhIDIwLjE5Mzk0NSw2LjQ4MzMxODggMCAwIDEgLTIwLjE5Mzk0NSw2LjQ4MzMyMyAyMC4xOTM5NDUsNi40ODMzMTg4IDAgMCAxIC0yMC4xOTM5NDUsLTYuNDgzMzIzIDIwLjE5Mzk0NSw2LjQ4MzMxODggMCAwIDEgMjAuMTkzOTQ1LC02LjQ4MzMyIDIwLjE5Mzk0NSw2LjQ4MzMxODggMCAwIDEgMjAuMTkzOTQ1LDYuNDgzMzIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJlbGxpcHNlMTQ4OTAiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2YzYjYyNjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC4yOTk4NTI7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgZD0ibSA5MC44Mjg2OTgsLTEwOC4zNzA1MSBhIDEyLjM3MjUyOSwzLjk3MjIzMjggMCAwIDEgLTEyLjM3MjUyOSwzLjk3MjI0IDEyLjM3MjUyOSwzLjk3MjIzMjggMCAwIDEgLTEyLjM3MjUyOSwtMy45NzIyNCAxMi4zNzI1MjksMy45NzIyMzI4IDAgMCAxIDEyLjM3MjUyOSwtMy45NzIyMyAxMi4zNzI1MjksMy45NzIyMzI4IDAgMCAxIDEyLjM3MjUyOSwzLjk3MjIzIHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0icGF0aDE1Njg3IgogICAgICAgICAgIHN0eWxlPSJzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC40OTQ3MDc7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgZD0ibSA4OS4wNDg0ODMsLTUxLjI1ODI0NCBhIDEwLjExODkzMyw3Ljg3OTUxOSAwIDAgMSAtMTAuMTE4OTMzLDcuODc5NTE5IDEwLjExODkzMyw3Ljg3OTUxOSAwIDAgMSAtMTAuMTE4OTMzLC03Ljg3OTUxOSAxMC4xMTg5MzMsNy44Nzk1MTkgMCAwIDEgMTAuMTE4OTMzLC03Ljg3OTUxOSAxMC4xMTg5MzMsNy44Nzk1MTkgMCAwIDEgMTAuMTE4OTMzLDcuODc5NTE5IHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0icGF0aDIxNjgxIgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNlM2I5MDA7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjEuMDAwMTk7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMC45OTk2MTU3MiwwLDAsMS4wMDAwMTE1LDUuMDczMTUyNiwzLjQ1NDU3MTMpIgogICAgICAgICAgIGQ9Ik0gNDMuOTQ4MjAxLDM4LjI1OTA3OSAyOS45NDI2MzMsNDYuMzQ1MTk4IDE1LjkzNzA2NCwzOC4yNTkwNzkgViAyMi4wODY4NDIgbCAxNC4wMDU1NjksLTguMDg2MTE5IDE0LjAwNTU2OCw4LjA4NjExOSB6IiAvPgogICAgICAgIDxwYXRoCiAgICAgICAgICAgaWQ9InVzZTI0MDg4IgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjE7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgZD0ibSA2My41MDQ1NTMsMTY2Ljc1Nzk1IC0xNC4wMDAxODYsOC4wODYyMSAtMTQuMDAwMTg3LC04LjA4NjIxIHYgLTE2LjE3MjQyIGwgMTQuMDAwMTg3LC04LjA4NjIyIDE0LjAwMDE4Niw4LjA4NjIyIHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0idXNlMjQxMDAiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MTtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MS40MTQyMSIKICAgICAgICAgICBkPSJtIDkyLjUwNDcyOCwxNjYuNzU3OTUgLTE0LjAwMDE4Niw4LjA4NjIxIC0xNC4wMDAxODcsLTguMDg2MjEgdiAtMTYuMTcyNDIgbCAxNC4wMDAxODcsLTguMDg2MjIgMTQuMDAwMTg2LDguMDg2MjIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA0MiIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gNDkuMDA0NDY1LDQxLjcxNDA5IDM1LjAwNDI3OSw0OS44MDAzMDIgMjEuMDA0MDkyLDQxLjcxNDA5IFYgMjUuNTQxNjY3IGwgMTQuMDAwMTg3LC04LjA4NjIxMiAxNC4wMDAxODYsOC4wODYyMTIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA0NCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gMzQuNTA0Mzc4LDY2LjcyMjg2MiAyMC41MDQxOTIsNzQuODA5MDc0IDYuNTA0MDA1Myw2Ni43MjI4NjIgViA1MC41NTA0MzkgbCAxNC4wMDAxODY3LC04LjA4NjIxMiAxNC4wMDAxODYsOC4wODYyMTIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA1NCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZjYzAwO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gNzguMDA0NjQxLDQxLjcxNDA5IDY0LjAwNDQ1NSw0OS44MDAzMDIgNTAuMDA0MjY4LDQxLjcxNDA5IFYgMjUuNTQxNjY3IGwgMTQuMDAwMTg3LC04LjA4NjIxMiAxNC4wMDAxODYsOC4wODYyMTIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA1NiIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gNjMuNTA0NTUzLDY2LjcyMjg2MiA0OS41MDQzNjcsNzQuODA5MDc0IDM1LjUwNDE4LDY2LjcyMjg2MiBWIDUwLjU1MDQzOSBsIDE0LjAwMDE4NywtOC4wODYyMTIgMTQuMDAwMTg2LDguMDg2MjEyIHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0idXNlMjQwNTgiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MTtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MS40MTQyMSIKICAgICAgICAgICBkPSJNIDQ5LjAwNDQ2NSw5MS43MzE2MzQgMzUuMDA0Mjc5LDk5LjgxNzg0NiAyMS4wMDQwOTIsOTEuNzMxNjM0IFYgNzUuNTU5MjExIGwgMTQuMDAwMTg3LC04LjA4NjIxMiAxNC4wMDAxODYsOC4wODYyMTIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA2MCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gMzQuNTA0Mzc4LDExNi43NDA0MSAyMC41MDQxOTIsMTI0LjgyNjYyIDYuNTA0MDA1MywxMTYuNzQwNDEgViAxMDAuNTY3OTggTCAyMC41MDQxOTIsOTIuNDgxNzcxIDM0LjUwNDM3OCwxMDAuNTY3OTggWiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA2NiIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gMTA3LjAwNDgyLDQxLjcxNDA5IDkzLjAwNDYzLDQ5LjgwMDMwMiA3OS4wMDQ0NDMsNDEuNzE0MDkgViAyNS41NDE2NjcgbCAxNC4wMDAxODcsLTguMDg2MjEyIDE0LjAwMDE5LDguMDg2MjEyIHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0idXNlMjQwNjgiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmY2MwMDtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MTtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MS40MTQyMSIKICAgICAgICAgICBkPSJNIDkyLjUwNDcyOCw2Ni43MjI4NjIgNzguNTA0NTQyLDc0LjgwOTA3NCA2NC41MDQzNTUsNjYuNzIyODYyIFYgNTAuNTUwNDM5IGwgMTQuMDAwMTg3LC04LjA4NjIxMiAxNC4wMDAxODYsOC4wODYyMTIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA3MCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZjYzAwO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gNzguMDA0NjQxLDkxLjczMTYzNCA2NC4wMDQ0NTUsOTkuODE3ODQ2IDUwLjAwNDI2OCw5MS43MzE2MzQgViA3NS41NTkyMTEgbCAxNC4wMDAxODcsLTguMDg2MjEyIDE0LjAwMDE4Niw4LjA4NjIxMiB6IiAvPgogICAgICAgIDxwYXRoCiAgICAgICAgICAgaWQ9InVzZTI0MDcyIgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNjODM3Mzc7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjE7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgZD0ibSA2My41MDQ1NTMsMTE2Ljc0MDQxIC0xNC4wMDAxODYsOC4wODYyMSAtMTQuMDAwMTg3LC04LjA4NjIxIHYgLTE2LjE3MjQzIGwgMTQuMDAwMTg3LC04LjA4NjIwOSAxNC4wMDAxODYsOC4wODYyMDkgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA3NCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Im0gNDkuMDA0NDY1LDE0MS43NDkxOCAtMTQuMDAwMTg2LDguMDg2MjEgLTE0LjAwMDE4NywtOC4wODYyMSB2IC0xNi4xNzI0MiBsIDE0LjAwMDE4NywtOC4wODYyMiAxNC4wMDAxODYsOC4wODYyMiB6IiAvPgogICAgICAgIDxwYXRoCiAgICAgICAgICAgaWQ9InVzZTI0MDc4IgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNjODM3Mzc7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjE7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgZD0iTSAxMzYuMDA0OTksNDEuNzE0MDkgMTIyLjAwNDgsNDkuODAwMzAyIDEwOC4wMDQ2Miw0MS43MTQwOSBWIDI1LjU0MTY2NyBsIDE0LjAwMDE4LC04LjA4NjIxMiAxNC4wMDAxOSw4LjA4NjIxMiB6IiAvPgogICAgICAgIDxwYXRoCiAgICAgICAgICAgaWQ9InVzZTI0MDgwIgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjE7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgZD0iTSAxMjEuNTA0OSw2Ni43MjI4NjIgMTA3LjUwNDcyLDc0LjgwOTA3NCA5My41MDQ1Myw2Ni43MjI4NjIgViA1MC41NTA0MzkgbCAxNC4wMDAxOSwtOC4wODYyMTIgMTQuMDAwMTgsOC4wODYyMTIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA4MiIKICAgICAgICAgICBzdHlsZT0iZmlsbDojYzgzNzM3O3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gMTA3LjAwNDgyLDkxLjczMTYzNCA5My4wMDQ2Myw5OS44MTc4NDYgNzkuMDA0NDQzLDkxLjczMTYzNCBWIDc1LjU1OTIxMSBsIDE0LjAwMDE4NywtOC4wODYyMTIgMTQuMDAwMTksOC4wODYyMTIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA4NCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Im0gOTIuNTA0NzI4LDExNi43NDA0MSAtMTQuMDAwMTg2LDguMDg2MjEgLTE0LjAwMDE4NywtOC4wODYyMSB2IC0xNi4xNzI0MyBsIDE0LjAwMDE4NywtOC4wODYyMDkgMTQuMDAwMTg2LDguMDg2MjA5IHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0idXNlMjQwODYiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmY2MwMDtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MTtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MS40MTQyMSIKICAgICAgICAgICBkPSJtIDc4LjAwNDY0MSwxNDEuNzQ5MTggLTE0LjAwMDE4Niw4LjA4NjIxIC0xNC4wMDAxODcsLTguMDg2MjEgdiAtMTYuMTcyNDIgbCAxNC4wMDAxODcsLTguMDg2MjIgMTQuMDAwMTg2LDguMDg2MjIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA5MiIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZjYzAwO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gMTUwLjUwNTA3LDY2LjcyMjg2MiAxMzYuNTA0ODksNzQuODA5MDc0IDEyMi41MDQ3LDY2LjcyMjg2MiBWIDUwLjU1MDQzOSBsIDE0LjAwMDE5LC04LjA4NjIxMiAxNC4wMDAxOCw4LjA4NjIxMiB6IiAvPgogICAgICAgIDxwYXRoCiAgICAgICAgICAgaWQ9InVzZTI0MDk0IgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjE7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgZD0iTSAxMzYuMDA0OTksOTEuNzMxNjM0IDEyMi4wMDQ4LDk5LjgxNzg0NiAxMDguMDA0NjIsOTEuNzMxNjM0IFYgNzUuNTU5MjExIGwgMTQuMDAwMTgsLTguMDg2MjEyIDE0LjAwMDE5LDguMDg2MjEyIHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0idXNlMjQwOTYiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmY2MwMDtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MTtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MS40MTQyMSIKICAgICAgICAgICBkPSJtIDEyMS41MDQ5LDExNi43NDA0MSAtMTQuMDAwMTgsOC4wODYyMSAtMTQuMDAwMTksLTguMDg2MjEgdiAtMTYuMTcyNDMgbCAxNC4wMDAxOSwtOC4wODYyMDkgMTQuMDAwMTgsOC4wODYyMDkgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA5OCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojYzgzNzM3O3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Im0gMTA3LjAwNDgyLDE0MS43NDkxOCAtMTQuMDAwMTksOC4wODYyMSAtMTQuMDAwMTg3LC04LjA4NjIxIHYgLTE2LjE3MjQyIGwgMTQuMDAwMTg3LC04LjA4NjIyIDE0LjAwMDE5LDguMDg2MjIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDEwOCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Im0gMTUwLjUwNTA3LDExNi43NDA0MSAtMTQuMDAwMTgsOC4wODYyMSAtMTQuMDAwMTksLTguMDg2MjEgdiAtMTYuMTcyNDMgbCAxNC4wMDAxOSwtOC4wODYyMDkgMTQuMDAwMTgsOC4wODYyMDkgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDExMCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Im0gMTM2LjAwNDk5LDE0MS43NDkxOCAtMTQuMDAwMTksOC4wODYyMSAtMTQuMDAwMTgsLTguMDg2MjEgdiAtMTYuMTcyNDIgbCAxNC4wMDAxOCwtOC4wODYyMiAxNC4wMDAxOSw4LjA4NjIyIHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0idXNlMjQxMTIiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MTtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MS40MTQyMSIKICAgICAgICAgICBkPSJtIDEyMS41MDQ5LDE2Ni43NTc5NSAtMTQuMDAwMTgsOC4wODYyMSAtMTQuMDAwMTksLTguMDg2MjEgdiAtMTYuMTcyNDIgbCAxNC4wMDAxOSwtOC4wODYyMiAxNC4wMDAxOCw4LjA4NjIyIHoiIC8+CiAgICAgIDwvZz4KICAgICAgPHJlY3QKICAgICAgICAgc3R5bGU9ImZpbGw6bm9uZTtmaWxsLW9wYWNpdHk6MTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6NC4zNjI4MTtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MS40MTQyMTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgICAgaWQ9InJlY3QyNDU4MiIKICAgICAgICAgd2lkdGg9IjEwNy4xNTE4NCIKICAgICAgICAgaGVpZ2h0PSIxMDcuMTUxODQiCiAgICAgICAgIHg9IjE2LjIxOTkxIgogICAgICAgICB5PSIxMy4zNjI4ODMiCiAgICAgICAgIHJ5PSIyMC4xNzk3OSIgLz4KICAgIDwvZz4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXNpemU6MTEzLjM4M3B4O2xpbmUtaGVpZ2h0OjEuMjU7Zm9udC1mYW1pbHk6c2Fucy1zZXJpZjtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjE7c3Ryb2tlLWRhc2hhcnJheTpub25lIgogICAgICAgeD0iMTc3LjcxMDM5IgogICAgICAgeT0iOTUuODk5MTc4IgogICAgICAgaWQ9InRleHQ2MyI+PHRzcGFuCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICAgIGlkPSJ0c3BhbjYxIgogICAgICAgICB4PSIxNzcuNzEwMzkiCiAgICAgICAgIHk9Ijk1Ljg5OTE3OCIKICAgICAgICAgc3R5bGU9ImZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7Zm9udC1zaXplOjExMy4zODNweDtmb250LWZhbWlseTonTm90byBTYW5zIE1vbm8nOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246J05vdG8gU2FucyBNb25vJztmaWxsOiMwMDAwMDA7c3Ryb2tlOiNmZmZmZmY7c3Ryb2tlLXdpZHRoOjE7c3Ryb2tlLWRhc2hhcnJheTpub25lIj5icGY8dHNwYW4KICAgc3R5bGU9ImZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7Zm9udC1mYW1pbHk6J05vdG8gU2FucyBNb25vJzstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOidOb3RvIFNhbnMgTW9ubyc7ZmlsbDojMDAwMDAwO3N0cm9rZTojZmZmZmZmO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZSIKICAgaWQ9InRzcGFuMTI5NzUiPmQ8dHNwYW4KICAgc3R5bGU9ImZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7Zm9udC1mYW1pbHk6J05vdG8gU2FucyBNb25vJzstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOidOb3RvIFNhbnMgTW9ubyc7ZmlsbDojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZSIKICAgaWQ9InRzcGFuMTMwMzEiIC8+PC90c3Bhbj48L3RzcGFuPjwvdGV4dD4KICA8L2c+Cjwvc3ZnPgo=
+ - base64data: |
+ PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjwh
+ RE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cu
+ dzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjwhLS0gQ3JlYXRlZCB3aXRo
+ IFZlY3Rvcm5hdG9yIChodHRwOi8vdmVjdG9ybmF0b3IuaW8vKSAtLT4KPHN2ZyBoZWlnaHQ9IjEw
+ MCUiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3R5bGU9ImZpbGwtcnVsZTpub256ZXJvO2NsaXAt
+ cnVsZTpldmVub2RkO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDsi
+ IHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgd2lkdGg9IjEwMCUiIHhtbDpz
+ cGFjZT0icHJlc2VydmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6
+ dmVjdG9ybmF0b3I9Imh0dHA6Ly92ZWN0b3JuYXRvci5pbyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93
+ d3cudzMub3JnLzE5OTkveGxpbmsiPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGdyYWRpZW50VHJh
+ bnNmb3JtPSJtYXRyaXgoMS40NTY4MyAxLjQ1NjgzIC0xLjQ1NjgzIDEuNDU2ODMgNzkxLjI0NCAt
+ NzA0LjEzMykiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiBpZD0iTGluZWFyR3JhZGll
+ bnQiIHgxPSIzNjguNDYyIiB4Mj0iMjQyLjMzMSIgeTE9IjQyNy4wNTMiIHkyPSI1NTMuNjE0Ij4K
+ PHN0b3Agb2Zmc2V0PSIwLjQyMjU5MiIgc3RvcC1jb2xvcj0iIzMzMzEyYyIvPgo8c3RvcCBvZmZz
+ ZXQ9IjEiIHN0b3AtY29sb3I9IiNmM2M2MjIiLz4KPC9saW5lYXJHcmFkaWVudD4KPHBhdGggZD0i
+ TTQzNS4xMTMgNDQ4LjQxM0M0MzUuMTEzIDM3NS43MjMgNDkzLjc5MyAyNjguNjkyIDUxMS4yMDkg
+ MjY4LjY5MkM1MjguNjI1IDI2OC42OTIgNTg4Ljg3MyAzNzAuOTU3IDU4OC44NzMgNDQzLjY0NkM1
+ ODguODczIDUxNi4zMzYgNTMyLjc4NSA2MDAuNzc3IDUxMS4yMDkgNjAwLjc3N0M0ODkuNjMzIDYw
+ MC43NzcgNDM1LjExMyA1MjEuMTAzIDQzNS4xMTMgNDQ4LjQxM1oiIGlkPSJGaWxsIi8+CjxwYXRo
+ IGQ9Ik00MzUuMTEzIDQ0OC40MTNDNDM1LjExMyAzNzUuNzIzIDQ5My43OTMgMjY4LjY5MiA1MTEu
+ MjA5IDI2OC42OTJDNTI4LjYyNSAyNjguNjkyIDU4OC44NzMgMzcwLjk1NyA1ODguODczIDQ0My42
+ NDZDNTg4Ljg3MyA1MTYuMzM2IDUzMi43ODUgNjAwLjc3NyA1MTEuMjA5IDYwMC43NzdDNDg5LjYz
+ MyA2MDAuNzc3IDQzNS4xMTMgNTIxLjEwMyA0MzUuMTEzIDQ0OC40MTNaIiBpZD0iRmlsbF8yIi8+
+ CjxwYXRoIGQ9Ik00MzUuMTEzIDQ0OC40MTNDNDM1LjExMyAzNzUuNzIzIDQ5My43OTMgMjY4LjY5
+ MiA1MTEuMjA5IDI2OC42OTJDNTI4LjYyNSAyNjguNjkyIDU4OC44NzMgMzcwLjk1NyA1ODguODcz
+ IDQ0My42NDZDNTg4Ljg3MyA1MTYuMzM2IDUzMi43ODUgNjAwLjc3NyA1MTEuMjA5IDYwMC43NzdD
+ NDg5LjYzMyA2MDAuNzc3IDQzNS4xMTMgNTIxLjEwMyA0MzUuMTEzIDQ0OC40MTNaIiBpZD0iRmls
+ bF8zIi8+CjxwYXRoIGQ9Ik00MzUuMTIgMzY4LjgzOEM0MzUuMTIgMzY4LjgzOCA0NzQuMjE5IDM3
+ OC4yNjMgNTEyLjAwNyAzNzguMzM1QzU0OS43OTYgMzc4LjQwNyA1ODguODggMzY5LjM4NSA1ODgu
+ ODggMzY5LjM4NSIgaWQ9IkZpbGxfNCIvPgo8L2RlZnM+CjxnIGlkPSJMYXllci0xIiB2ZWN0b3Ju
+ YXRvcjpsYXllck5hbWU9IkxheWVyIDEiPgo8ZyBvcGFjaXR5PSIxIiB2ZWN0b3JuYXRvcjpsYXll
+ ck5hbWU9Ikdyb3VwIDEyIj4KPHBhdGggZD0iTTE4Ni4yMzMgMzg3LjI3OEMxODYuMjMzIDIwNy4z
+ NjQgMzMyLjA4NCA2MS41MTM5IDUxMS45OTggNjEuNTEzOUM2OTEuOTE2IDYxLjUxMzkgODM3Ljc2
+ NyAyMDcuMzY0IDgzNy43NjcgMzg3LjI3OEM4MzcuNzY3IDU2Ny4xOTMgNjkxLjkxNiA3MTMuMDQz
+ IDUxMS45OTggNzEzLjA0M0MzMzIuMDg0IDcxMy4wNDMgMTg2LjIzMyA1NjcuMTkzIDE4Ni4yMzMg
+ Mzg3LjI3OCIgZmlsbD0iI2YyOGYyMiIgZmlsbC1ydWxlPSJub256ZXJvIiBvcGFjaXR5PSIxIiBz
+ dHJva2U9Im5vbmUiIHZlY3Rvcm5hdG9yOmxheWVyTmFtZT0icGF0aCIvPgo8ZyBvcGFjaXR5PSIx
+ IiB2ZWN0b3JuYXRvcjpsYXllck5hbWU9Ikdyb3VwIDEzIj4KPHBhdGggZD0iTTIwMS4zMzEgNDg1
+ LjQ4NUMyMDQuODQ3IDQ4MS41MzQgMjA4LjE4MiA0NzcuNTkzIDIxMS4yOTQgNDczLjY2QzI3MS4z
+ ODUgMzk3LjYzNiAyNDkuMTUgMzQzLjc2OSAxOTguMTU5IDI5OS45OTFDMTkwLjQ0MyAzMjcuNzg0
+ IDE4Ni4yMzUgMzU3LjAzIDE4Ni4yMzUgMzg3LjI3N0MxODYuMjM1IDQyMS41MDkgMTkxLjU0NCA0
+ NTQuNDkgMjAxLjMzMSA0ODUuNDg1IiBmaWxsPSIjZTBmMjIyIiBmaWxsLXJ1bGU9Im5vbnplcm8i
+ IG9wYWNpdHk9IjEiIHN0cm9rZT0ibm9uZSIgdmVjdG9ybmF0b3I6bGF5ZXJOYW1lPSJwYXRoIi8+
+ CjxwYXRoIGQ9Ik00MjEuMTYzIDQwNS4wOThDNDQzLjY2IDMyMy4yNzkgMzQxLjE0MSAyNTIuNjQx
+ IDI0Ni4xNzkgMTk5LjA4QzIzOSAyMDkuMTk4IDIzMi4zNDIgMjE5LjcwMyAyMjYuMzM4IDIzMC42
+ MjlDMzA5LjI2NSAyNzguMTY5IDM5MC43NjkgMzQyLjM1OSAzNTYuNjU0IDQyMy4zMDFDMzM0LjIy
+ NSA0NzYuNTA1IDI3OC43MTMgNTEzLjMwNyAyMzIuNzA4IDU1NS4wMDFDMjM4LjYxNCA1NjQuODE4
+ IDI0NS4wMjcgNTc0LjI5MiAyNTEuOTA0IDU4My4zOTZDMzA5Ljk0NCA1MjQuODAxIDM5OS4zNTMg
+ NDg0LjQyNyA0MjEuMTYzIDQwNS4wOTgiIGZpbGw9IiNlMGYyMjIiIGZpbGwtcnVsZT0ibm9uemVy
+ byIgb3BhY2l0eT0iMSIgc3Ryb2tlPSJub25lIiB2ZWN0b3JuYXRvcjpsYXllck5hbWU9InBhdGgi
+ Lz4KPHBhdGggZD0iTTU5Ny45MjMgMzIzLjgwN0M2MjkuMjkyIDQ0Mi45OTkgNDU0LjQwMiA1MTku
+ MzQxIDM4OC45NiA1OTIuMzA1QzM2Ni4zMDQgNjE3LjU2NiAzNDguODAxIDYzOS4xODcgMzM5LjMw
+ NiA2NjMuNDY0QzM0OC43NTggNjY5LjM4NyAzNTguNTE1IDY3NC44NTggMzY4LjU4NiA2NzkuODAx
+ QzM3My43NzQgNjU2LjU2NSAzODQuNTUgNjMyLjg1MSA0MDIuODQgNjA4LjIzNUM0NzEuODAyIDUx
+ NS40MTcgNjUwLjg2NSA0NTUuNTg1IDYyNi4zMTMgMzE4LjkwM0M2MDcuNzE4IDIxNS4zODYgNDk4
+ LjE3NiAxNjEuODQyIDQ2MS45MjQgNjUuMzQ5N0M0MzguOTk3IDY4Ljg4NzIgNDE2Ljg5NSA3NC44
+ NzQ4IDM5NS44MDggODIuOTI5OEM0NDcuMzAxIDE3My44MTMgNTcwLjgyNiAyMjAuODQxIDU5Ny45
+ MjMgMzIzLjgwNyIgZmlsbD0iI2UwZjIyMiIgZmlsbC1ydWxlPSJub256ZXJvIiBvcGFjaXR5PSIx
+ IiBzdHJva2U9Im5vbmUiIHZlY3Rvcm5hdG9yOmxheWVyTmFtZT0icGF0aCIvPgo8cGF0aCBkPSJN
+ NzA1LjQ4MSAzMDQuMzA1Qzc0MC4zNzkgNDYwLjIwOSA1MTkuNjMxIDUyMy4yNjUgNDQ2LjAyNiA2
+ MzAuMzQ1QzQzMC40ODQgNjUyLjk0OSA0MjMuMDc4IDY3Ni4zNzEgNDIxLjI1OSA3MDAuMTQxQzQz
+ NC4zMjYgNzAzLjkyMyA0NDcuNjk4IDcwNi45NzUgNDYxLjM4NCA3MDkuMTExQzQ2Mi43NzIgNjg0
+ LjQ2OSA0NjkuOCA2NjAuMjI2IDQ4NS4xMTUgNjM2Ljg4N0M1NTguODAxIDUyNC41ODUgNzg2Ljk3
+ MiA0NjguMDg4IDc2MC4xMDggMzA4LjUxM0M3NDcuMzg1IDIzMi45MjcgNzAwLjQ0MyAxNzQuMzU0
+ IDY3NS4zNDEgMTA1LjQ2NUM2NTAuMDI4IDkwLjc2MDggNjIyLjU5NiA3OS4yOTMgNTkzLjU0OSA3
+ MS44MDUzQzYxOC4xODYgMTU1Ljg1OSA2ODUuNzY0IDIxNi4yMzcgNzA1LjQ4MSAzMDQuMzA1IiBm
+ aWxsPSIjZTBmMjIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIG9wYWNpdHk9IjEiIHN0cm9rZT0ibm9u
+ ZSIgdmVjdG9ybmF0b3I6bGF5ZXJOYW1lPSJwYXRoIi8+CjxwYXRoIGQ9Ik0zMjQuMzM5IDYxMS4y
+ MjNDMzgxLjUzNyA1MzUuNzk2IDU2My4wOCA0NjMuODc3IDU1Mi43MyAzNTIuNzQ4QzU0My41OTIg
+ MjU0LjY0OCA0MDIuNjYzIDE5NC4wNzYgMzE2LjE1NSAxMjYuOTYyQzMwNi40NDEgMTM0LjI4MiAy
+ OTcuMiAxNDIuMTc0IDI4OC4zNzUgMTUwLjUxM0MzODUuNjg1IDIxMS44NTQgNTE1Ljk3MSAyNzcu
+ NDc2IDUxMy40MzUgMzY1LjIzOUM1MTAuMDk5IDQ4MC42MzIgMzQ3LjM3NCA1MzMuNDMyIDI4NC44
+ ODEgNjIwLjcxM0MyOTEuNzU0IDYyNy40MDIgMjk4Ljg5MyA2MzMuODEgMzA2LjMzNCA2MzkuODc1
+ QzMxMS4xNzQgNjMwLjI4MSAzMTcuMTMxIDYyMC43MjYgMzI0LjMzOSA2MTEuMjIzIiBmaWxsPSIj
+ ZTBmMjIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIG9wYWNpdHk9IjEiIHN0cm9rZT0ibm9uZSIgdmVj
+ dG9ybmF0b3I6bGF5ZXJOYW1lPSJwYXRoIi8+CjxwYXRoIGQ9Ik02MzguNDgxIDYyOS41MjRDNjc3
+ LjY2OCA1NjEuMTY0IDc1MS4wODggNTI2LjczOSA4MjEuNTI0IDQ4OC44NjZDODIzLjkxIDQ4MS41
+ ODkgODI2LjA4NCA0NzQuMjE4IDgyNy45NjMgNDY2LjcyMUM3NTkuNDc4IDUxNC4zNjQgNjc2LjQy
+ NiA1NDcuODQzIDYyNC42NzkgNjE3LjQzM0M2MDQuOTcxIDY0My45MzYgNTk3Ljc2NyA2NzIuMTkz
+ IDU5OC41MTEgNzAxLjM0QzYwNi43MTYgNjk5LjA4NCA2MTQuODE0IDY5Ni41NzMgNjIyLjc0NCA2
+ OTMuNzA2QzYyMi4yNzYgNjcxLjQ0NiA2MjYuNzkzIDY0OS45MDcgNjM4LjQ4MSA2MjkuNTI0IiBm
+ aWxsPSIjZTBmMjIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIG9wYWNpdHk9IjEiIHN0cm9rZT0ibm9u
+ ZSIgdmVjdG9ybmF0b3I6bGF5ZXJOYW1lPSJwYXRoIi8+CjxwYXRoIGQ9Ik02ODYuNDcxIDY0NC43
+ NjRDNjgzLjU3OCA2NTEuNzQ0IDY4MS43IDY1OC44NjIgNjgwLjYxMiA2NjYuMDYyQzczMS43MzYg
+ NjM1LjA3NiA3NzMuNTgxIDU5MC4zODIgODAxLjIyNyA1MzcuMTI2Qzc0OS41NDkgNTY0LjQzMyA3
+ MDYuMTc0IDU5Ny4yMjUgNjg2LjQ3MSA2NDQuNzY0IiBmaWxsPSIjZTBmMjIyIiBmaWxsLXJ1bGU9
+ Im5vbnplcm8iIG9wYWNpdHk9IjEiIHN0cm9rZT0ibm9uZSIgdmVjdG9ybmF0b3I6bGF5ZXJOYW1l
+ PSJwYXRoIi8+CjxwYXRoIGQ9Ik04MzYuNjY2IDQxMi45MTlDODM3LjMyOCA0MDQuNDQ3IDgzNy43
+ NjcgMzk1LjkxNSA4MzcuNzY3IDM4Ny4yOEM4MzcuNzY3IDM3MC42OCA4MzYuNTA3IDM1NC4zODEg
+ ODM0LjExMyAzMzguNDUxQzgyMi42OCA0NzIuMzY1IDYzNC41MjEgNTIwLjkyMyA1NjMuMzkzIDYy
+ MC4wNjhDNTQwLjY1OSA2NTEuNzU5IDUzMC41NjIgNjgyLjM3NiA1MjguNjE5IDcxMi42MjNDNTM4
+ LjA0MSA3MTIuMTUgNTQ3LjMzNCA3MTEuMTk2IDU1Ni41MzMgNzA5LjkzN0M1NTcuNDc4IDY4Mi44
+ MjMgNTY0Ljg1OCA2NTUuNTggNTgxLjg3MSA2MjguMDY3QzYzNy45ODEgNTM3LjMyNSA3NzQuNjg5
+ IDQ5OC43MDUgODM2LjY2NiA0MTIuOTE5IiBmaWxsPSIjZTBmMjIyIiBmaWxsLXJ1bGU9Im5vbnpl
+ cm8iIG9wYWNpdHk9IjEiIHN0cm9rZT0ibm9uZSIgdmVjdG9ybmF0b3I6bGF5ZXJOYW1lPSJwYXRo
+ Ii8+CjwvZz4KPC9nPgo8ZyBvcGFjaXR5PSIxIiB2ZWN0b3JuYXRvcjpsYXllck5hbWU9Ikdyb3Vw
+ IDEzIj4KPHBhdGggZD0iTTQzNS4xMTMgNDQ4LjQxM0M0MzUuMTEzIDM3NS43MjMgNDkzLjc5MyAy
+ NjguNjkyIDUxMS4yMDkgMjY4LjY5MkM1MjguNjI1IDI2OC42OTIgNTg4Ljg3MyAzNzAuOTU3IDU4
+ OC44NzMgNDQzLjY0NkM1ODguODczIDUxNi4zMzYgNTMyLjc4NSA2MDAuNzc3IDUxMS4yMDkgNjAw
+ Ljc3N0M0ODkuNjMzIDYwMC43NzcgNDM1LjExMyA1MjEuMTAzIDQzNS4xMTMgNDQ4LjQxM1oiIGZp
+ bGw9InVybCgjTGluZWFyR3JhZGllbnQpIiBmaWxsLXJ1bGU9Im5vbnplcm8iIG9wYWNpdHk9IjEi
+ IHN0cm9rZT0iIzMzMzEyYyIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0i
+ cm91bmQiIHN0cm9rZS13aWR0aD0iMjQuNDgxOSIgdmVjdG9ybmF0b3I6bGF5ZXJOYW1lPSJPdmFs
+ IDEiLz4KPHBhdGggZD0iTTQzOS4yMzMgMjY4LjY5MkM0MzkuMjMzIDI2OC42OTIgNDQxLjE1MiAx
+ OTUuOTMzIDUxMS45OTMgMTk1LjkzM0M1ODIuODM0IDE5NS45MzMgNTg0Ljc1MiAyNjguNjkyIDU4
+ NC43NTIgMjY4LjY5Mkw0MzkuMjMzIDI2OC42OTJaIiBmaWxsPSIjZjNjNjIyIiBmaWxsLXJ1bGU9
+ Im5vbnplcm8iIG9wYWNpdHk9IjEiIHN0cm9rZT0iIzMzMzEyYyIgc3Ryb2tlLWxpbmVjYXA9ImJ1
+ dHQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMjQuNDgxOSIgdmVjdG9y
+ bmF0b3I6bGF5ZXJOYW1lPSJPdmFsIDIiLz4KPHBhdGggZD0iTTQ4MC4xODggMTk1LjkzM0M0ODAu
+ MTg4IDE5NS45MzMgNDY3LjUxOCAxNjYuMzQ1IDQ0Mi4zMjQgMTc1LjU1OCIgZmlsbD0ibm9uZSIg
+ b3BhY2l0eT0iMSIgc3Ryb2tlPSIjMzMzMTJjIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9r
+ ZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMjQuNDgxOSIgdmVjdG9ybmF0b3I6bGF5
+ ZXJOYW1lPSJDdXJ2ZSAyIi8+CjxwYXRoIGQ9Ik01NDUuMzM3IDE5NS45MzNDNTQ1LjMzNyAxOTUu
+ OTMzIDU1OC4wMDYgMTY2LjM0NSA1ODMuMjAxIDE3NS41NTgiIGZpbGw9Im5vbmUiIG9wYWNpdHk9
+ IjEiIHN0cm9rZT0iIzMzMzEyYyIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpv
+ aW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjI0LjQ4MTkiIHZlY3Rvcm5hdG9yOmxheWVyTmFtZT0i
+ Q3VydmUgMyIvPgo8ZyBvcGFjaXR5PSIxIiB2ZWN0b3JuYXRvcjpsYXllck5hbWU9Ikdyb3VwIDIi
+ Pgo8dXNlIGZpbGw9Im5vbmUiIG9wYWNpdHk9IjEiIHN0cm9rZT0iI2YyYWMyMiIgc3Ryb2tlLWxp
+ bmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMjQuNDgx
+ OSIgdmVjdG9ybmF0b3I6bGF5ZXJOYW1lPSJPdmFsIDUiIHhsaW5rOmhyZWY9IiNGaWxsIi8+Cjxj
+ bGlwUGF0aCBjbGlwLXJ1bGU9Im5vbnplcm8iIGlkPSJDbGlwUGF0aCI+Cjx1c2UgeGxpbms6aHJl
+ Zj0iI0ZpbGwiLz4KPC9jbGlwUGF0aD4KPGcgY2xpcC1wYXRoPSJ1cmwoI0NsaXBQYXRoKSI+Cjxw
+ YXRoIGQ9Ik00MzUuMTEzIDQyNS4yMzhDNDM1LjExMyA0MjUuMjM4IDQ3NC4yMTIgNDM0LjY2MyA1
+ MTIgNDM0LjczNUM1NDkuNzg4IDQzNC44MDcgNTg4Ljg3MyA0MjUuNzg1IDU4OC44NzMgNDI1Ljc4
+ NSIgZmlsbD0ibm9uZSIgb3BhY2l0eT0iMSIgc3Ryb2tlPSIjZjJkZTIyIiBzdHJva2UtbGluZWNh
+ cD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLXdpZHRoPSIyNC40ODE5IiB2
+ ZWN0b3JuYXRvcjpsYXllck5hbWU9IkN1cnZlIDQiLz4KPC9nPgo8L2c+CjxnIG9wYWNpdHk9IjEi
+ IHZlY3Rvcm5hdG9yOmxheWVyTmFtZT0iR3JvdXAgMyI+Cjx1c2UgZmlsbD0ibm9uZSIgb3BhY2l0
+ eT0iMSIgc3Ryb2tlPSIjZjNjNjIyIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVq
+ b2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyNC40ODE5IiB2ZWN0b3JuYXRvcjpsYXllck5hbWU9
+ Ik92YWwgNCIgeGxpbms6aHJlZj0iI0ZpbGxfMiIvPgo8Y2xpcFBhdGggY2xpcC1ydWxlPSJub256
+ ZXJvIiBpZD0iQ2xpcFBhdGhfMiI+Cjx1c2UgeGxpbms6aHJlZj0iI0ZpbGxfMiIvPgo8L2NsaXBQ
+ YXRoPgo8ZyBjbGlwLXBhdGg9InVybCgjQ2xpcFBhdGhfMikiPgo8cGF0aCBkPSJNNDM5LjIzMyA0
+ ODEuNjUzQzQzOS4yMzMgNDgxLjY1MyA0NzUuNjIgNDkzLjg5MiA1MTIgNDkzLjg5MkM1NDguMzgg
+ NDkzLjg5MiA1ODQuNzUyIDQ4MS42NTMgNTg0Ljc1MiA0ODEuNjUzIiBmaWxsPSJub25lIiBvcGFj
+ aXR5PSIxIiBzdHJva2U9IiNmM2M2MjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxp
+ bmVqb2luPSJtaXRlciIgc3Ryb2tlLXdpZHRoPSIyNC40ODE5IiB2ZWN0b3JuYXRvcjpsYXllck5h
+ bWU9IkN1cnZlIDUiLz4KPC9nPgo8L2c+CjxnIG9wYWNpdHk9IjEiIHZlY3Rvcm5hdG9yOmxheWVy
+ TmFtZT0iR3JvdXAgNCI+Cjx1c2UgZmlsbD0ibm9uZSIgb3BhY2l0eT0iMSIgc3Ryb2tlPSIjZjJk
+ ZTIyIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tl
+ LXdpZHRoPSIyNC40ODE5IiB2ZWN0b3JuYXRvcjpsYXllck5hbWU9Ik92YWwgMyIgeGxpbms6aHJl
+ Zj0iI0ZpbGxfMyIvPgo8Y2xpcFBhdGggY2xpcC1ydWxlPSJub256ZXJvIiBpZD0iQ2xpcFBhdGhf
+ MyI+Cjx1c2UgeGxpbms6aHJlZj0iI0ZpbGxfMyIvPgo8L2NsaXBQYXRoPgo8ZyBjbGlwLXBhdGg9
+ InVybCgjQ2xpcFBhdGhfMykiPgo8cGF0aCBkPSJNNDYxLjI1NiA1NDAuMTc0QzQ2MS4yNTYgNTQw
+ LjE3NCA0ODcuNDU5IDU0OS41ODYgNTExLjk5MyA1NDkuNTIzQzUzNi41MjcgNTQ5LjQ2MSA1NTku
+ MzkxIDUzOS45MjUgNTU5LjM5MSA1MzkuOTI1IiBmaWxsPSJub25lIiBvcGFjaXR5PSIxIiBzdHJv
+ a2U9IiNmMmRlMjIiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiIHN0cm9rZS1saW5lam9pbj0ibWl0
+ ZXIiIHN0cm9rZS13aWR0aD0iMjQuNDgxOSIgdmVjdG9ybmF0b3I6bGF5ZXJOYW1lPSJDdXJ2ZSA2
+ Ii8+CjwvZz4KPC9nPgo8ZyBvcGFjaXR5PSIxIiB2ZWN0b3JuYXRvcjpsYXllck5hbWU9Ikdyb3Vw
+ IDEiPgo8dXNlIGZpbGw9Im5vbmUiIG9wYWNpdHk9IjEiIHN0cm9rZT0iI2YzYzYyMiIgc3Ryb2tl
+ LWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS13aWR0aD0iMjQu
+ NDgxOSIgdmVjdG9ybmF0b3I6bGF5ZXJOYW1lPSJDdXJ2ZSA3IiB4bGluazpocmVmPSIjRmlsbF80
+ Ii8+CjxjbGlwUGF0aCBjbGlwLXJ1bGU9Im5vbnplcm8iIGlkPSJDbGlwUGF0aF80Ij4KPHVzZSB4
+ bGluazpocmVmPSIjRmlsbF80Ii8+CjwvY2xpcFBhdGg+CjxnIGNsaXAtcGF0aD0idXJsKCNDbGlw
+ UGF0aF80KSI+CjxwYXRoIGQ9Ik00MzUuMTEzIDQ0OC40MTNDNDM1LjExMyAzNzUuNzIzIDQ5My43
+ OTMgMjY4LjY5MiA1MTEuMjA5IDI2OC42OTJDNTI4LjYyNSAyNjguNjkyIDU4OC44NzMgMzcwLjk1
+ NyA1ODguODczIDQ0My42NDZDNTg4Ljg3MyA1MTYuMzM2IDUzMi43ODUgNjAwLjc3NyA1MTEuMjA5
+ IDYwMC43NzdDNDg5LjYzMyA2MDAuNzc3IDQzNS4xMTMgNTIxLjEwMyA0MzUuMTEzIDQ0OC40MTNa
+ IiBmaWxsPSJub25lIiBvcGFjaXR5PSIxIiBzdHJva2U9IiNmM2M2MjIiIHN0cm9rZS1saW5lY2Fw
+ PSJidXR0IiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjI0LjQ4MTkiIHZl
+ Y3Rvcm5hdG9yOmxheWVyTmFtZT0iT3ZhbCA0Ii8+CjwvZz4KPC9nPgo8cGF0aCBkPSJNNDM1LjEx
+ MyA0NDguNDEzQzQzNS4xMTMgMzc1LjcyMyA0OTMuNzkzIDI2OC42OTIgNTExLjIwOSAyNjguNjky
+ QzUyOC42MjUgMjY4LjY5MiA1ODguODczIDM3MC45NTcgNTg4Ljg3MyA0NDMuNjQ2QzU4OC44NzMg
+ NTE2LjMzNiA1MzIuNzg1IDYwMC43NzcgNTExLjIwOSA2MDAuNzc3QzQ4OS42MzMgNjAwLjc3NyA0
+ MzUuMTEzIDUyMS4xMDMgNDM1LjExMyA0NDguNDEzWiIgZmlsbD0ibm9uZSIgb3BhY2l0eT0iMSIg
+ c3Ryb2tlPSIjMzMzMTJjIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJy
+ b3VuZCIgc3Ryb2tlLXdpZHRoPSIyNC40ODE5IiB2ZWN0b3JuYXRvcjpsYXllck5hbWU9Ik92YWwg
+ MyIvPgo8cGF0aCBkPSJNNDkwLjQ1MyAyNjkuNDQ2QzQ1MC4xMjUgMjY5LjgwMyAzNjEuNTgxIDI4
+ NS40MjggMjk4LjI2OSAzMjUuNTU2QzE5NS4wNDcgMzkwLjk3OCAyNjQuMjI0IDQ2OS4wODkgMzI2
+ LjIxMSA0NjguMjI5QzQyMi4xODYgNDY2Ljg5NyA1MTEuOTg5IDMwMC4yNjIgNTExLjk4OSAyNzMu
+ ODU2QzUxMS45ODkgMjcyLjExMiA1MDkuMTIgMjcwLjgzOSA1MDMuOTYxIDI3MC4xMkM1MDAuNDU1
+ IDI2OS42MzEgNDk1Ljg5MiAyNjkuMzk4IDQ5MC40NTMgMjY5LjQ0NlpNNTExLjk4OSAyNzMuODU2
+ QzUxMS45ODkgMzAwLjI2MiA2MDEuNzkyIDQ2Ni44OTcgNjk3Ljc2NyA0NjguMjI5Qzc1OS43NTQg
+ NDY5LjA4OSA4MjguOTYzIDM5MC45NzggNzI1Ljc0MSAzMjUuNTU2QzY1NC4yMzkgMjgwLjIzNyA1
+ NTAuNTA5IDI2Ni4xOTIgNTIwLjQ0NSAyNzAuMDc2QzUxNS4wMTYgMjcwLjc3NyA1MTEuOTg5IDI3
+ Mi4wNjQgNTExLjk4OSAyNzMuODU2WiIgZmlsbD0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJub256ZXJv
+ IiBvcGFjaXR5PSIxIiBzdHJva2U9IiMzMzMxMmMiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJv
+ a2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjI0LjQ4MTkiIHZlY3Rvcm5hdG9yOmxh
+ eWVyTmFtZT0iQ3VydmUgMSIvPgo8L2c+CjxnIG9wYWNpdHk9IjEiIHZlY3Rvcm5hdG9yOmxheWVy
+ TmFtZT0iR3JvdXAgMTQiPgo8cGF0aCBkPSJNMTU0LjkxOSA5MjQuNTMzQzE0NS4zOTQgOTI0LjUz
+ MyAxMzcuMTYxIDkyMi41MTcgMTMwLjIyMSA5MTguNDg1QzEyMy4yOCA5MTQuNDU0IDExNy45Njkg
+ OTA4LjI2NCAxMTQuMjg2IDg5OS45MThDMTEwLjYwMyA4OTEuNTcxIDEwOC43NjEgODgwLjk5MiAx
+ MDguNzYxIDg2OC4xODJDMTA4Ljc2MSA4NTUuMzQzIDExMC42ODQgODQ0Ljc4NCAxMTQuNTMxIDgz
+ Ni41MDZDMTE4LjM3NyA4MjguMjI4IDEyMy43ODQgODIyLjA2IDEzMC43NTEgODE4LjAwMkMxMzcu
+ NzE4IDgxMy45NDQgMTQ1Ljc3NCA4MTEuOTE0IDE1NC45MTkgODExLjkxNEMxNjUuMjY1IDgxMS45
+ MTQgMTc0LjU1MSA4MTQuMjIyIDE4Mi43NzggODE4LjgzN0MxOTEuMDA1IDgyMy40NTIgMTk3LjUy
+ IDgyOS45NjcgMjAyLjMyNCA4MzguMzgxQzIwNy4xMjggODQ2Ljc5NiAyMDkuNTMgODU2LjczIDIw
+ OS41MyA4NjguMTgyQzIwOS41MyA4NzkuNjM1IDIwNy4xMjggODg5LjU2OSAyMDIuMzI0IDg5Ny45
+ ODNDMTk3LjUyIDkwNi4zOTggMTkxLjAwNSA5MTIuOTI3IDE4Mi43NzggOTE3LjU2OUMxNzQuNTUx
+ IDkyMi4yMTIgMTY1LjI2NSA5MjQuNTMzIDE1NC45MTkgOTI0LjUzM1pNOTAuMzA5NyA5MjIuOTg3
+ TDkwLjMwOTcgNzcxLjkxM0wxMjIuMDcyIDc3MS45MTNMMTIyLjA3MiA4MzUuNTcxTDEyMC4wMzYg
+ ODY4LjA1OEwxMjAuNTcgOTAwLjU0OUwxMjAuNTcgOTIyLjk4N0w5MC4zMDk3IDkyMi45ODdaTTE0
+ OS40NTQgODk4LjUyNkMxNTQuNzMgODk4LjUyNiAxNTkuNDYzIDg5Ny4zMjIgMTYzLjY1MiA4OTQu
+ OTE1QzE2Ny44NDEgODkyLjUwOCAxNzEuMTc5IDg4OS4wMDcgMTczLjY2OCA4ODQuNDEyQzE3Ni4x
+ NTcgODc5LjgxNyAxNzcuNDAxIDg3NC40MDcgMTc3LjQwMSA4NjguMTgyQzE3Ny40MDEgODYxLjgy
+ MiAxNzYuMTU3IDg1Ni4zOTEgMTczLjY2OCA4NTEuODkxQzE3MS4xNzkgODQ3LjM5IDE2Ny44NDEg
+ ODQzLjkzNyAxNjMuNjUyIDg0MS41MjlDMTU5LjQ2MyA4MzkuMTIyIDE1NC43MyA4MzcuOTE5IDE0
+ OS40NTQgODM3LjkxOUMxNDQuMTc3IDgzNy45MTkgMTM5LjQ0NCA4MzkuMTIyIDEzNS4yNTQgODQx
+ LjUyOUMxMzEuMDY0IDg0My45MzcgMTI3LjcyNSA4NDcuMzkgMTI1LjIzNiA4NTEuODkxQzEyMi43
+ NDcgODU2LjM5MSAxMjEuNTAzIDg2MS44MjIgMTIxLjUwMyA4NjguMTgyQzEyMS41MDMgODc0LjQw
+ NyAxMjIuNzQ3IDg3OS44MTcgMTI1LjIzNiA4ODQuNDEyQzEyNy43MjUgODg5LjAwNyAxMzEuMDY0
+ IDg5Mi41MDggMTM1LjI1NCA4OTQuOTE1QzEzOS40NDQgODk3LjMyMiAxNDQuMTc3IDg5OC41MjYg
+ MTQ5LjQ1NCA4OTguNTI2WiIgZmlsbD0iI2YzYzYyMiIgZmlsbC1ydWxlPSJub256ZXJvIiBvcGFj
+ aXR5PSIxIiBzdHJva2U9Im5vbmUiLz4KPHBhdGggZD0iTTI5NS40NzYgOTI0LjUzM0MyODYuMzMx
+ IDkyNC41MzMgMjc4LjI3NSA5MjIuNTA0IDI3MS4zMDggOTE4LjQ0NkMyNjQuMzQxIDkxNC4zODcg
+ MjU4LjkzNCA5MDguMTk4IDI1NS4wODggODk5Ljg3OEMyNTEuMjQyIDg5MS41NTggMjQ5LjMxOSA4
+ ODEuMDE5IDI0OS4zMTkgODY4LjI2MkMyNDkuMzE5IDg1NS4zMTYgMjUxLjE2IDg0NC43MDQgMjU0
+ Ljg0MyA4MzYuNDI1QzI1OC41MjYgODI4LjE0NiAyNjMuODM4IDgyMS45OTEgMjcwLjc3OCA4MTcu
+ OTYxQzI3Ny43MTkgODEzLjkzIDI4NS45NTEgODExLjkxNCAyOTUuNDc2IDgxMS45MTRDMzA1Ljgy
+ MiA4MTEuOTE0IDMxNS4xMDggODE0LjIzNSAzMjMuMzM1IDgxOC44NzdDMzMxLjU2MiA4MjMuNTE4
+ IDMzOC4wNzcgODMwLjA0NiAzNDIuODgxIDgzOC40NjFDMzQ3LjY4NSA4NDYuODc2IDM1MC4wODcg
+ ODU2LjgwOSAzNTAuMDg3IDg2OC4yNjJDMzUwLjA4NyA4NzkuNzE3IDM0Ny42ODUgODg5LjY1MSAz
+ NDIuODgxIDg5OC4wNjZDMzM4LjA3NyA5MDYuNDgxIDMzMS41NjIgOTEyLjk5NSAzMjMuMzM1IDkx
+ Ny42MUMzMTUuMTA4IDkyMi4yMjUgMzA1LjgyMiA5MjQuNTMzIDI5NS40NzYgOTI0LjUzM1pNMjMw
+ Ljg2NyA5NjIuNDg2TDIzMC44NjcgODEzLjQ1N0wyNjEuMTI4IDgxMy40NTdMMjYxLjEyOCA4MzUu
+ ODk1TDI2MC41OTMgODY4LjM4NkwyNjIuNjI5IDkwMC44NzdMMjYyLjYyOSA5NjIuNDg2TDIzMC44
+ NjcgOTYyLjQ4NlpNMjkwLjAxMSA4OTguNTI2QzI5NS4yODggODk4LjUyNiAzMDAuMDIgODk3LjMy
+ MiAzMDQuMjA5IDg5NC45MTVDMzA4LjM5OCA4OTIuNTA4IDMxMS43MzYgODg5LjAyIDMxNC4yMjUg
+ ODg0LjQ1MkMzMTYuNzE0IDg3OS44ODMgMzE3Ljk1OSA4NzQuNDg3IDMxNy45NTkgODY4LjI2MkMz
+ MTcuOTU5IDg2MS45MDEgMzE2LjcxNCA4NTYuNDU4IDMxNC4yMjUgODUxLjkzMUMzMTEuNzM2IDg0
+ Ny40MDQgMzA4LjM5OCA4NDMuOTM3IDMwNC4yMDkgODQxLjUyOUMzMDAuMDIgODM5LjEyMiAyOTUu
+ Mjg4IDgzNy45MTkgMjkwLjAxMSA4MzcuOTE5QzI4NC43MzQgODM3LjkxOSAyODAuMDAxIDgzOS4x
+ MjIgMjc1LjgxMSA4NDEuNTI5QzI3MS42MjEgODQzLjkzNyAyNjguMjgyIDg0Ny40MDQgMjY1Ljc5
+ MyA4NTEuOTMxQzI2My4zMDQgODU2LjQ1OCAyNjIuMDYgODYxLjkwMSAyNjIuMDYgODY4LjI2MkMy
+ NjIuMDYgODc0LjQ4NyAyNjMuMzA0IDg3OS44ODMgMjY1Ljc5MyA4ODQuNDUyQzI2OC4yODIgODg5
+ LjAyIDI3MS42MjEgODkyLjUwOCAyNzUuODExIDg5NC45MTVDMjgwLjAwMSA4OTcuMzIyIDI4NC43
+ MzQgODk4LjUyNiAyOTAuMDExIDg5OC41MjZaIiBmaWxsPSIjZjNjNjIyIiBmaWxsLXJ1bGU9Im5v
+ bnplcm8iIG9wYWNpdHk9IjEiIHN0cm9rZT0ibm9uZSIvPgo8cGF0aCBkPSJNMzc1LjI4NiA5MjIu
+ OTg3TDM3NS4yODYgODEwLjk3QzM3NS4yODYgNzk4LjYzIDM3OC45NDEgNzg4Ljc3OCAzODYuMjQ5
+ IDc4MS40MTRDMzkzLjU1OCA3NzQuMDQ5IDQwNC4wMDYgNzcwLjM2NiA0MTcuNTk1IDc3MC4zNjZD
+ NDIyLjE1MiA3NzAuMzY2IDQyNi41ODEgNzcwLjgyOCA0MzAuODggNzcxLjc1MkM0MzUuMTc5IDc3
+ Mi42NzYgNDM4LjgyIDc3NC4xMjkgNDQxLjgwNCA3NzYuMTEyTDQzMy41MTQgNzk5LjExQzQzMS43
+ NzUgNzk3LjkzOSA0MjkuNzk4IDc5NyA0MjcuNTgyIDc5Ni4yOTNDNDI1LjM2NyA3OTUuNTg1IDQy
+ My4wNjMgNzk1LjIzMiA0MjAuNjcxIDc5NS4yMzJDNDE2LjAyMiA3OTUuMjMyIDQxMi40MzMgNzk2
+ LjU1NyA0MDkuOTA1IDc5OS4yMDhDNDA3LjM3NyA4MDEuODU5IDQwNi4xMTMgODA1Ljg3NCA0MDYu
+ MTEzIDgxMS4yNTNMNDA2LjExMyA4MjEuNDYyTDQwNy4wNDkgODM1LjExNkw0MDcuMDQ5IDkyMi45
+ ODdMMzc1LjI4NiA5MjIuOTg3Wk0zNTguMjk1IDg0MC4yNkwzNTguMjk1IDgxNS44ODJMNDM0LjI3
+ NCA4MTUuODgyTDQzNC4yNzQgODQwLjI2TDM1OC4yOTUgODQwLjI2WiIgZmlsbD0iI2YzYzYyMiIg
+ ZmlsbC1ydWxlPSJub256ZXJvIiBvcGFjaXR5PSIxIiBzdHJva2U9Im5vbmUiLz4KPC9nPgo8ZyBv
+ cGFjaXR5PSIxIiB2ZWN0b3JuYXRvcjpsYXllck5hbWU9Ikdyb3VwIDE1Ij4KPHBhdGggZD0iTTYy
+ Mi45MDYgODExLjkxNEM2MzEuNTY4IDgxMS45MTQgNjM5LjI1OCA4MTMuNjI0IDY0NS45NzcgODE3
+ LjA0NEM2NTIuNjk2IDgyMC40NjQgNjU3Ljk2OSA4MjUuNzM3IDY2MS43OTYgODMyLjg2MkM2NjUu
+ NjIzIDgzOS45ODcgNjY3LjUzNyA4NDkuMTQgNjY3LjUzNyA4NjAuMzIxTDY2Ny41MzcgOTIyLjk4
+ N0w2MzUuNzc1IDkyMi45ODdMNjM1Ljc3NSA4NjUuMDY4QzYzNS43NzUgODU2LjI2MiA2MzMuOTQ1
+ IDg0OS43NjUgNjMwLjI4NyA4NDUuNTc4QzYyNi42MjggODQxLjM5IDYyMS41MzMgODM5LjI5NiA2
+ MTUuMDAxIDgzOS4yOTZDNjEwLjMyIDgzOS4yOTYgNjA2LjE1NyA4NDAuMzQzIDYwMi41MTEgODQy
+ LjQzOEM1OTguODY1IDg0NC41MzIgNTk2LjAyOSA4NDcuNjczIDU5NC4wMDMgODUxLjg2MUM1OTEu
+ OTc2IDg1Ni4wNDggNTkwLjk2MyA4NjEuNDMyIDU5MC45NjMgODY4LjAxMUw1OTAuOTYzIDkyMi45
+ ODdMNTU5LjE5NyA5MjIuOTg3TDU1OS4xOTcgODY1LjA2OEM1NTkuMTk3IDg1Ni4yNjIgNTU3LjM5
+ NSA4NDkuNzY1IDU1My43OTEgODQ1LjU3OEM1NTAuMTg2IDg0MS4zOSA1NDUuMDY0IDgzOS4yOTYg
+ NTM4LjQyMyA4MzkuMjk2QzUzMy43NDUgODM5LjI5NiA1MjkuNTgyIDg0MC4zNDMgNTI1LjkzNSA4
+ NDIuNDM4QzUyMi4yODggODQ0LjUzMiA1MTkuNDUyIDg0Ny42NzMgNTE3LjQyNSA4NTEuODYxQzUx
+ NS4zOTkgODU2LjA0OCA1MTQuMzg1IDg2MS40MzIgNTE0LjM4NSA4NjguMDExTDUxNC4zODUgOTIy
+ Ljk4N0w0ODIuNjIzIDkyMi45ODdMNDgyLjYyMyA4MTMuNDU3TDUxMi44ODQgODEzLjQ1N0w1MTIu
+ ODg0IDg0My4zMzZMNTA3LjExOSA4MzQuNjIzQzUxMC45MTggODI3LjE4OSA1MTYuMzI1IDgyMS41
+ NDYgNTIzLjM0MiA4MTcuNjkzQzUzMC4zNTkgODEzLjg0MSA1MzguMzM0IDgxMS45MTQgNTQ3LjI2
+ NyA4MTEuOTE0QzU1Ny4yODYgODExLjkxNCA1NjYuMDYzIDgxNC40MzkgNTczLjU5NiA4MTkuNDg4
+ QzU4MS4xMjkgODI0LjUzNiA1ODYuMTQ0IDgzMi4zMTIgNTg4LjY0IDg0Mi44MTVMNTc3LjQyIDgz
+ OS43MTZDNTgxLjA4MSA4MzEuMjI0IDU4Ni45MzYgODI0LjQ2NyA1OTQuOTg3IDgxOS40NDZDNjAz
+ LjAzOCA4MTQuNDI1IDYxMi4zNDQgODExLjkxNCA2MjIuOTA2IDgxMS45MTRaIiBmaWxsPSIjOTk5
+ OTk5IiBmaWxsLXJ1bGU9Im5vbnplcm8iIG9wYWNpdHk9IjEiIHN0cm9rZT0ibm9uZSIvPgo8cGF0
+ aCBkPSJNNzYzLjIzOCA5MjIuOTg3TDc2My4yMzggOTAxLjY0TDc2MS4zMzMgODk2LjgzNkw3NjEu
+ MzMzIDg1OC42MzhDNzYxLjMzMyA4NTEuODE2IDc1OS4yNiA4NDYuNTI5IDc1NS4xMTUgODQyLjc3
+ OEM3NTAuOTY5IDgzOS4wMjcgNzQ0LjU5MSA4MzcuMTUyIDczNS45ODEgODM3LjE1MkM3MzAuMjIg
+ ODM3LjE1MiA3MjQuNTE2IDgzOC4wNTUgNzE4Ljg2NyA4MzkuODYxQzcxMy4yMTggODQxLjY2NyA3
+ MDguMzk3IDg0NC4xMzIgNzA0LjQwMyA4NDcuMjU2TDY5My4wNjIgODI1LjE1MkM2OTkuMDg1IDgy
+ MC44MzQgNzA2LjI5NiA4MTcuNTQ4IDcxNC42OTQgODE1LjI5NUM3MjMuMDkzIDgxMy4wNDEgNzMx
+ LjYzNiA4MTEuOTE0IDc0MC4zMjMgODExLjkxNEM3NTcuMDg3IDgxMS45MTQgNzcwLjA3MyA4MTUu
+ ODQyIDc3OS4yODIgODIzLjY5NkM3ODguNDkgODMxLjU1MSA3OTMuMDk1IDg0My43OTMgNzkzLjA5
+ NSA4NjAuNDIzTDc5My4wOTUgOTIyLjk4N0w3NjMuMjM4IDkyMi45ODdaTTcyOS45NTggOTI0LjUz
+ M0M3MjEuNDM5IDkyNC41MzMgNzE0LjEyNSA5MjMuMDk1IDcwOC4wMTcgOTIwLjIxOEM3MDEuOTA5
+ IDkxNy4zNDEgNjk3LjIzMiA5MTMuMzkyIDY5My45ODcgOTA4LjM3QzY5MC43NDIgOTAzLjM0OSA2
+ ODkuMTIgODk3LjY5IDY4OS4xMiA4OTEuMzkzQzY4OS4xMiA4ODQuOTM3IDY5MC43MDYgODc5LjI1
+ OSA2OTMuODc5IDg3NC4zNTlDNjk3LjA1MiA4NjkuNDU5IDcwMi4wOTggODY1LjYxNyA3MDkuMDE3
+ IDg2Mi44MzNDNzE1LjkzNyA4NjAuMDQ5IDcyNC45NjEgODU4LjY1OCA3MzYuMDg5IDg1OC42NThM
+ NzY1LjA3NCA4NTguNjU4TDc2NS4wNzQgODc3LjE2Nkw3MzkuNjExIDg3Ny4xNjZDNzMyLjEwMyA4
+ NzcuMTY2IDcyNi45NjMgODc4LjM3NyA3MjQuMTkxIDg4MC43OThDNzIxLjQxOSA4ODMuMjE5IDcy
+ MC4wMzMgODg2LjMxOCA3MjAuMDMzIDg5MC4wOTVDNzIwLjAzMyA4OTQuMDY2IDcyMS42MTEgODk3
+ LjI0NyA3MjQuNzY3IDg5OS42NDFDNzI3LjkyMiA5MDIuMDM0IDczMi4yNjIgOTAzLjIzMSA3Mzcu
+ Nzg0IDkwMy4yMzFDNzQzLjExOCA5MDMuMjMxIDc0Ny45MDcgOTAxLjk5MyA3NTIuMTUxIDg5OS41
+ MThDNzU2LjM5NSA4OTcuMDQzIDc1OS40NTYgODkzLjMxOCA3NjEuMzMzIDg4OC4zNDJMNzY2LjEz
+ IDkwMy4wOTFDNzYzLjg3NSA5MTAuMDYgNzU5LjY5MyA5MTUuMzc2IDc1My41ODEgOTE5LjAzOUM3
+ NDcuNDcgOTIyLjcwMiA3MzkuNTk2IDkyNC41MzMgNzI5Ljk1OCA5MjQuNTMzWiIgZmlsbD0iIzk5
+ OTk5OSIgZmlsbC1ydWxlPSJub256ZXJvIiBvcGFjaXR5PSIxIiBzdHJva2U9Im5vbmUiLz4KPHBh
+ dGggZD0iTTg4OC4yMDcgODExLjkxNEM4OTYuOTIyIDgxMS45MTQgOTA0LjY5OSA4MTMuNjI0IDkx
+ MS41NCA4MTcuMDQ0QzkxOC4zODEgODIwLjQ2NCA5MjMuNzgzIDgyNS43MzcgOTI3Ljc0NiA4MzIu
+ ODYyQzkzMS43MDkgODM5Ljk4NyA5MzMuNjkgODQ5LjE0IDkzMy42OSA4NjAuMzIxTDkzMy42OSA5
+ MjIuOTg3TDkwMS45MjggOTIyLjk4N0w5MDEuOTI4IDg2NS4wNjhDOTAxLjkyOCA4NTYuMjYyIDg5
+ OS45OSA4NDkuNzY1IDg5Ni4xMTMgODQ1LjU3OEM4OTIuMjM2IDg0MS4zOSA4ODYuODAxIDgzOS4y
+ OTYgODc5LjgwOCA4MzkuMjk2Qzg3NC43MiA4MzkuMjk2IDg3MC4xNzcgODQwLjM1NiA4NjYuMTgg
+ ODQyLjQ3N0M4NjIuMTgyIDg0NC41OTggODU5LjA3NSA4NDcuODIgODU2Ljg1OCA4NTIuMTQyQzg1
+ NC42NDIgODU2LjQ2NSA4NTMuNTM0IDg2Mi4wMjMgODUzLjUzNCA4NjguODE5TDg1My41MzQgOTIy
+ Ljk4N0w4MjEuNzcyIDkyMi45ODdMODIxLjc3MiA4MTMuNDU3TDg1Mi4wMzIgODEzLjQ1N0w4NTIu
+ MDMyIDg0My44MjNMODQ2LjM0NyA4MzQuNzAyQzg1MC4yODEgODI3LjI3MSA4NTUuOTA3IDgyMS42
+ MTUgODYzLjIyMyA4MTcuNzM1Qzg3MC41MzkgODEzLjg1NSA4NzguODY3IDgxMS45MTQgODg4LjIw
+ NyA4MTEuOTE0WiIgZmlsbD0iIzk5OTk5OSIgZmlsbC1ydWxlPSJub256ZXJvIiBvcGFjaXR5PSIx
+ IiBzdHJva2U9Im5vbmUiLz4KPC9nPgo8L2c+Cjwvc3ZnPgo=
mediatype: image/svg+xml
install:
spec:
diff --git a/bpfman-operator/config/openshift/manager_auth_proxy_patch.yaml b/bpfman-operator/config/openshift/manager_auth_proxy_patch.yaml
index 61acfd03f..db945cf75 100644
--- a/bpfman-operator/config/openshift/manager_auth_proxy_patch.yaml
+++ b/bpfman-operator/config/openshift/manager_auth_proxy_patch.yaml
@@ -30,13 +30,13 @@ spec:
allowPrivilegeEscalation: false
capabilities:
drop:
- - "ALL"
+ - ALL
image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.0
args:
- - "--secure-listen-address=0.0.0.0:8443"
- - "--upstream=http://127.0.0.1:8080/"
- - "--logtostderr=true"
- - "--v=0"
+ - --secure-listen-address=0.0.0.0:8443
+ - --upstream=http://127.0.0.1:8174/
+ - --logtostderr=true
+ - --v=0
ports:
- containerPort: 8443
protocol: TCP
@@ -50,6 +50,6 @@ spec:
memory: 64Mi
- name: bpfman-operator
args:
- - "--health-probe-bind-address=:8081"
- - "--metrics-bind-address=127.0.0.1:8080"
- - "--leader-elect"
+ - --health-probe-bind-address=:8175
+ - --metrics-bind-address=127.0.0.1:8174
+ - --leader-elect
diff --git a/bpfman-operator/config/openshift/patch.yaml b/bpfman-operator/config/openshift/patch.yaml
index 61b8be2ab..cda171a2d 100644
--- a/bpfman-operator/config/openshift/patch.yaml
+++ b/bpfman-operator/config/openshift/patch.yaml
@@ -8,8 +8,8 @@ metadata:
pod-security.kubernetes.io/warn: privileged
annotations:
openshift.io/node-selector: ""
- openshift.io/description: "Openshift bpfman components"
- workload.openshift.io/allowed: "management"
+ openshift.io/description: Openshift bpfman components
+ workload.openshift.io/allowed: management
name: system
---
apiVersion: v1
@@ -19,5 +19,5 @@ metadata:
namespace: kube-system
data:
## Can be configured at runtime
- bpfman.log.level: "info"
- bpfman.agent.log.level: "info"
+ bpfman.log.level: info
+ bpfman.agent.log.level: info
diff --git a/bpfman-operator/config/rbac/bpfman-agent/role.yaml b/bpfman-operator/config/rbac/bpfman-agent/role.yaml
index db7f89e55..0ca66a515 100644
--- a/bpfman-operator/config/rbac/bpfman-agent/role.yaml
+++ b/bpfman-operator/config/rbac/bpfman-agent/role.yaml
@@ -31,6 +31,34 @@ rules:
- get
- patch
- update
+- apiGroups:
+ - bpfman.io
+ resources:
+ - fentryprograms
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - bpfman.io
+ resources:
+ - fentryprograms/finalizers
+ verbs:
+ - update
+- apiGroups:
+ - bpfman.io
+ resources:
+ - fexitprograms
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - bpfman.io
+ resources:
+ - fexityprograms/finalizers
+ verbs:
+ - update
- apiGroups:
- bpfman.io
resources:
@@ -109,6 +137,14 @@ rules:
- get
- list
- watch
+- apiGroups:
+ - ""
+ resources:
+ - pods
+ verbs:
+ - get
+ - list
+ - watch
- apiGroups:
- ""
resources:
diff --git a/bpfman-operator/config/rbac/bpfman-operator/role.yaml b/bpfman-operator/config/rbac/bpfman-operator/role.yaml
index 270feceeb..790bf6e0c 100644
--- a/bpfman-operator/config/rbac/bpfman-operator/role.yaml
+++ b/bpfman-operator/config/rbac/bpfman-operator/role.yaml
@@ -31,6 +31,58 @@ rules:
- configmaps/finalizers
verbs:
- update
+- apiGroups:
+ - bpfman.io
+ resources:
+ - fentryprograms
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - bpfman.io
+ resources:
+ - fentryprograms/finalizers
+ verbs:
+ - update
+- apiGroups:
+ - bpfman.io
+ resources:
+ - fentryprograms/status
+ verbs:
+ - get
+ - patch
+ - update
+- apiGroups:
+ - bpfman.io
+ resources:
+ - fexitprograms
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - bpfman.io
+ resources:
+ - fexitprograms/finalizers
+ verbs:
+ - update
+- apiGroups:
+ - bpfman.io
+ resources:
+ - fexitprograms/status
+ verbs:
+ - get
+ - patch
+ - update
- apiGroups:
- bpfman.io
resources:
diff --git a/bpfman-operator/config/samples/bpfman.io_v1alpha1_fentry_fentryprogram.yaml b/bpfman-operator/config/samples/bpfman.io_v1alpha1_fentry_fentryprogram.yaml
new file mode 100644
index 000000000..48f8231a4
--- /dev/null
+++ b/bpfman-operator/config/samples/bpfman.io_v1alpha1_fentry_fentryprogram.yaml
@@ -0,0 +1,14 @@
+apiVersion: bpfman.io/v1alpha1
+kind: FentryProgram
+metadata:
+ labels:
+ app.kubernetes.io/name: fentryprogram
+ name: fentry-example
+spec:
+ bpffunctionname: test_fentry
+ # Select all nodes
+ nodeselector: {}
+ func_name: do_unlinkat
+ bytecode:
+ image:
+ url: quay.io/bpfman-bytecode/fentry:latest
diff --git a/bpfman-operator/config/samples/bpfman.io_v1alpha1_fexit_fexitprogram.yaml b/bpfman-operator/config/samples/bpfman.io_v1alpha1_fexit_fexitprogram.yaml
new file mode 100644
index 000000000..2e7be6a88
--- /dev/null
+++ b/bpfman-operator/config/samples/bpfman.io_v1alpha1_fexit_fexitprogram.yaml
@@ -0,0 +1,14 @@
+apiVersion: bpfman.io/v1alpha1
+kind: FexitProgram
+metadata:
+ labels:
+ app.kubernetes.io/name: fexitprogram
+ name: fexit-example
+spec:
+ bpffunctionname: test_fexit
+ # Select all nodes
+ nodeselector: {}
+ func_name: do_unlinkat
+ bytecode:
+ image:
+ url: quay.io/bpfman-bytecode/fexit:latest
diff --git a/bpfman-operator/config/samples/bpfman.io_v1alpha1_kprobe_kprobeprogram.yaml b/bpfman-operator/config/samples/bpfman.io_v1alpha1_kprobe_kprobeprogram.yaml
index 3c605b8b4..7bd978925 100644
--- a/bpfman-operator/config/samples/bpfman.io_v1alpha1_kprobe_kprobeprogram.yaml
+++ b/bpfman-operator/config/samples/bpfman.io_v1alpha1_kprobe_kprobeprogram.yaml
@@ -8,8 +8,7 @@ spec:
bpffunctionname: my_kprobe
# Select all nodes
nodeselector: {}
- func_names:
- - try_to_wake_up
+ func_name: try_to_wake_up
offset: 0
retprobe: false
bytecode:
diff --git a/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram.yaml b/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram.yaml
index 7e1ad1b43..d4f113519 100644
--- a/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram.yaml
+++ b/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram.yaml
@@ -5,13 +5,12 @@ metadata:
app.kubernetes.io/name: uprobeprogram
name: uprobe-example
spec:
- bpffunctionname: my_uprobe
# Select all nodes
nodeselector: {}
+ bpffunctionname: my_uprobe
func_name: syscall
# offset: 0 # optional offset w/in function
- target:
- - /bpfman
+ target: bpfman
retprobe: false
# pid: 0 # optional pid to execute uprobe for
bytecode:
diff --git a/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_containers.yaml b/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_containers.yaml
new file mode 100644
index 000000000..3e43ef67d
--- /dev/null
+++ b/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_containers.yaml
@@ -0,0 +1,34 @@
+apiVersion: bpfman.io/v1alpha1
+kind: UprobeProgram
+metadata:
+ labels:
+ app.kubernetes.io/name: uprobeprogram
+ name: uprobe-example-containers
+spec:
+ # Select all nodes
+ nodeselector: {}
+ bpffunctionname: my_uprobe
+ func_name: malloc
+ # 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:
+ namespace: bpfman
+ pods:
+ matchLabels:
+ name: bpfman-daemon
+ containernames:
+ - bpfman
+ - bpfman-agent
diff --git a/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_containers_all.yaml b/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_containers_all.yaml
new file mode 100644
index 000000000..70e83df8c
--- /dev/null
+++ b/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_containers_all.yaml
@@ -0,0 +1,31 @@
+apiVersion: bpfman.io/v1alpha1
+kind: UprobeProgram
+metadata:
+ labels:
+ app.kubernetes.io/name: uprobeprogram
+ name: uprobe-example-containers-all
+spec:
+ bpffunctionname: my_uprobe
+ # Select all nodes
+ nodeselector: {}
+ func_name: malloc
+ # 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
+ # This is only a test. Don't ever do this in production. It will try to
+ # install the uprobe in every container on every node and probably crash the
+ # cluster.
+ containers:
+ pods: {}
diff --git a/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_nginx.yaml b/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_nginx.yaml
new file mode 100644
index 000000000..0f4d61d39
--- /dev/null
+++ b/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_nginx.yaml
@@ -0,0 +1,33 @@
+# CRD can be used with the nginx-deployment.yaml in this directory
+apiVersion: bpfman.io/v1alpha1
+kind: UprobeProgram
+metadata:
+ labels:
+ app.kubernetes.io/name: uprobeprogram
+ name: uprobe-example-nginx
+spec:
+ # Select all nodes
+ nodeselector: {}
+ bpffunctionname: my_uprobe
+ func_name: malloc
+ # 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:
+ namespace: default
+ pods:
+ matchLabels: {}
+ containernames:
+ - nginx
diff --git a/bpfman-operator/config/samples/kustomization.yaml b/bpfman-operator/config/samples/kustomization.yaml
index 9748e1a05..e6f2d9428 100644
--- a/bpfman-operator/config/samples/kustomization.yaml
+++ b/bpfman-operator/config/samples/kustomization.yaml
@@ -5,4 +5,6 @@ resources:
- 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
# +kubebuilder:scaffold:manifestskustomizesamples
diff --git a/bpfman-operator/config/test/manager_auth_proxy_patch.yaml b/bpfman-operator/config/test/manager_auth_proxy_patch.yaml
index 61acfd03f..db945cf75 100644
--- a/bpfman-operator/config/test/manager_auth_proxy_patch.yaml
+++ b/bpfman-operator/config/test/manager_auth_proxy_patch.yaml
@@ -30,13 +30,13 @@ spec:
allowPrivilegeEscalation: false
capabilities:
drop:
- - "ALL"
+ - ALL
image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.0
args:
- - "--secure-listen-address=0.0.0.0:8443"
- - "--upstream=http://127.0.0.1:8080/"
- - "--logtostderr=true"
- - "--v=0"
+ - --secure-listen-address=0.0.0.0:8443
+ - --upstream=http://127.0.0.1:8174/
+ - --logtostderr=true
+ - --v=0
ports:
- containerPort: 8443
protocol: TCP
@@ -50,6 +50,6 @@ spec:
memory: 64Mi
- name: bpfman-operator
args:
- - "--health-probe-bind-address=:8081"
- - "--metrics-bind-address=127.0.0.1:8080"
- - "--leader-elect"
+ - --health-probe-bind-address=:8175
+ - --metrics-bind-address=127.0.0.1:8174
+ - --leader-elect
diff --git a/bpfman-operator/config/test/patch.yaml b/bpfman-operator/config/test/patch.yaml
index a63067e24..2d06f90a5 100644
--- a/bpfman-operator/config/test/patch.yaml
+++ b/bpfman-operator/config/test/patch.yaml
@@ -6,4 +6,5 @@ metadata:
data:
bpfman.agent.image: quay.io/bpfman/bpfman-agent:int-test
bpfman.image: quay.io/bpfman/bpfman:int-test
- bpfman.log.level: debug
+ bpfman.log.level: bpfman=debug
+ bpfman.agent.log.level: debug
diff --git a/bpfman-operator/controllers/bpfman-agent/common.go b/bpfman-operator/controllers/bpfman-agent/common.go
index 14636e3e3..a26957ef2 100644
--- a/bpfman-operator/controllers/bpfman-agent/common.go
+++ b/bpfman-operator/controllers/bpfman-agent/common.go
@@ -28,6 +28,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -50,6 +52,9 @@ import (
//+kubebuilder:rbac:groups=bpfman.io,resources=tracepointprograms/finalizers,verbs=update
//+kubebuilder:rbac:groups=bpfman.io,resources=kprobeprograms/finalizers,verbs=update
//+kubebuilder:rbac:groups=bpfman.io,resources=uprobeprograms/finalizers,verbs=update
+//+kubebuilder:rbac:groups=bpfman.io,resources=fentryprograms/finalizers,verbs=update
+//+kubebuilder:rbac:groups=bpfman.io,resources=fexityprograms/finalizers,verbs=update
+//+kubebuilder:rbac:groups=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
@@ -102,6 +107,29 @@ func nodePredicate(nodeName string) predicate.Funcs {
}
}
+// Predicate to watch for Pod events on a specific node which checks if the
+// event's Pod is scheduled on the given node.
+func podOnNodePredicate(nodeName string) predicate.Funcs {
+ return predicate.Funcs{
+ GenericFunc: func(e event.GenericEvent) bool {
+ pod, ok := e.Object.(*v1.Pod)
+ return ok && pod.Spec.NodeName == nodeName
+ },
+ CreateFunc: func(e event.CreateEvent) bool {
+ pod, ok := e.Object.(*v1.Pod)
+ return ok && pod.Spec.NodeName == nodeName && pod.Status.Phase == v1.PodRunning
+ },
+ UpdateFunc: func(e event.UpdateEvent) bool {
+ pod, ok := e.ObjectNew.(*v1.Pod)
+ return ok && pod.Spec.NodeName == nodeName && pod.Status.Phase == v1.PodRunning
+ },
+ DeleteFunc: func(e event.DeleteEvent) bool {
+ pod, ok := e.Object.(*v1.Pod)
+ return ok && pod.Spec.NodeName == nodeName
+ },
+ }
+}
+
func isNodeSelected(selector *metav1.LabelSelector, nodeLabels map[string]string) (bool, error) {
// Logic to check if this node is selected by the *Program object
selectorTool, err := metav1.LabelSelectorAsSelector(selector)
@@ -248,6 +276,172 @@ func (r *ReconcilerCommon) createBpfProgram(ctx context.Context,
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
+// bpfPrograms. Typically this happens when containers that used to match a
+// container selector are deleted and the eBPF programs that were installed in
+// them need to be removed. This function handles both of these cases.
+//
+// For the first case, deletion of a *Program takes a few steps if there are
+// existing bpfPrograms:
+// 1. Reconcile the bpfProgram (take bpfman cleanup steps).
+// 2. Remove any finalizers from the bpfProgram Object.
+// 3. Update the condition on the bpfProgram to BpfProgCondUnloaded so the
+// operator knows it's safe to remove the parent Program Object, which
+// is when the bpfProgram is automatically deleted by the owner-reference.
+//
+// For the second case, we need to do the first 2 steps, and then explicitly
+// delete the bpfPrograms that are no longer needed.
+func handleProgDelete(
+ ctx context.Context,
+ r *ReconcilerCommon,
+ common *bpfmaniov1alpha1.BpfProgramCommon,
+ rec bpfmanReconciler,
+ existingPrograms map[string]bpfmaniov1alpha1.BpfProgram,
+ programMap map[string]*gobpfman.ListResponse_ListResult,
+ isNodeSelected bool,
+ isBeingDeleted bool,
+ mapOwnerStatus *MapOwnerParamStatus,
+) (internal.ReconcileResult, error) {
+ for _, prog := range existingPrograms {
+ // Reconcile the bpfProgram if error write condition and exit with
+ // retry.
+ cond, err := rec.reconcileBpfmanProgram(ctx,
+ programMap,
+ &common.ByteCode,
+ &prog,
+ isNodeSelected,
+ true, // delete program
+ mapOwnerStatus,
+ )
+ if err != nil {
+ r.updateStatus(ctx, &prog, cond)
+ return internal.Requeue, fmt.Errorf("failed to delete bpfman program: %v", err)
+ }
+
+ if r.removeFinalizer(ctx, &prog, rec.getFinalizer()) {
+ return internal.Updated, nil
+ }
+
+ if isBeingDeleted {
+ // 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, &prog, cond) {
+ return internal.Updated, nil
+ }
+ } else {
+ // We're deleting these programs because they were not expected due
+ // to changes that caused the containers to not be selected anymore.
+ // So, explicitly delete them.
+ opts := client.DeleteOptions{}
+ r.Logger.Info("Deleting bpfProgram", "Name", prog.Name, "Owner", prog.GetName())
+ if err := r.Delete(ctx, &prog, &opts); err != nil {
+ return internal.Requeue, fmt.Errorf("failed to create bpfProgram object: %v", err)
+ }
+ return internal.Updated, nil
+ }
+ }
+
+ // We're done reconciling.
+ r.Logger.Info("Nothing to do for this program")
+ return internal.Unchanged, nil
+}
+
+// handleProgCreateOrUpdate compares the expected bpfPrograms to the existing
+// 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 handleProgCreateOrUpdate(
+ ctx context.Context,
+ r *ReconcilerCommon,
+ common *bpfmaniov1alpha1.BpfProgramCommon,
+ rec bpfmanReconciler,
+ program client.Object,
+ existingPrograms map[string]bpfmaniov1alpha1.BpfProgram,
+ expectedPrograms *bpfmaniov1alpha1.BpfProgramList,
+ programMap map[string]*gobpfman.ListResponse_ListResult,
+ isNodeSelected bool,
+ isBeingDeleted bool,
+ mapOwnerStatus *MapOwnerParamStatus,
+) (internal.ReconcileResult, error) {
+ // If the *Program isn't being deleted ALWAYS create the bpfPrograms
+ // even if the node isn't selected
+ for _, expectedProg := range expectedPrograms.Items {
+ prog, exists := existingPrograms[expectedProg.Name]
+ if exists {
+ // Remove the bpfProgram from the existingPrograms map so we know
+ // not to delete it below.
+ delete(existingPrograms, expectedProg.Name)
+ } else {
+ // Create a new bpfProgram Object for this program.
+ opts := client.CreateOptions{}
+ r.Logger.Info("Creating bpfProgram", "Name", expectedProg.Name, "Owner", program.GetName())
+ if err := r.Create(ctx, &expectedProg, &opts); err != nil {
+ return internal.Requeue, fmt.Errorf("failed to create bpfProgram object: %v", err)
+ }
+ existingPrograms[expectedProg.Name] = prog
+ return internal.Updated, nil
+ }
+
+ // bpfProgram Object exists go ahead and reconcile it, if there is
+ // an error write condition and exit with retry.
+ cond, err := rec.reconcileBpfmanProgram(ctx,
+ programMap,
+ &common.ByteCode,
+ &prog,
+ isNodeSelected,
+ isBeingDeleted,
+ mapOwnerStatus,
+ )
+ if err != nil {
+ if r.updateStatus(ctx, &prog, cond) {
+ // Return an error the first time.
+ return internal.Updated, fmt.Errorf("failed to reconcile bpfman program: %v", err)
+ }
+ } else {
+ // Make sure if we're not selected exit and write correct condition
+ if cond == bpfmaniov1alpha1.BpfProgCondNotSelected ||
+ cond == bpfmaniov1alpha1.BpfProgCondMapOwnerNotFound ||
+ cond == bpfmaniov1alpha1.BpfProgCondMapOwnerNotLoaded ||
+ cond == bpfmaniov1alpha1.BpfProgCondNoContainersOnNode {
+ // Write NodeNodeSelected status
+ if r.updateStatus(ctx, &prog, cond) {
+ r.Logger.V(1).Info("Update condition from bpfman reconcile", "condition", cond)
+ return internal.Updated, nil
+ } else {
+ continue
+ }
+ }
+
+ existingId, err := bpfmanagentinternal.GetID(&prog)
+ if err != nil {
+ return internal.Requeue, fmt.Errorf("failed to get kernel id from bpfProgram: %v", err)
+ }
+
+ // 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("Updating bpfProgram Object", "Id", r.progId, "bpfProgram", prog.Name)
+ // annotations should be populate on create
+ prog.Annotations[internal.IdAnnotation] = strconv.FormatUint(uint64(*r.progId), 10)
+ if err := r.Update(ctx, &prog, &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, &prog, cond) {
+ return internal.Updated, nil
+ }
+ }
+ }
+
+ // We're done reconciling the expected programs. If any unexpected programs
+ // exist, delete them and return the result.
+ return handleProgDelete(ctx, r, common, rec, existingPrograms, programMap, isNodeSelected, isBeingDeleted, mapOwnerStatus)
+}
+
// reconcileProgram is called by ALL *Program controllers, and contains much of
// the core logic for taking *Program objects, turning them into bpfProgram
// object(s), and ultimately telling the custom controller types to load real
@@ -282,14 +476,6 @@ func reconcileProgram(ctx context.Context,
return internal.Requeue, fmt.Errorf("failed to get existing bpfPrograms: %v", err)
}
- // Generate the list of BpfPrograms for this *Program. This handles the one
- // *Program to many BpfPrograms (i.e. One *Program maps to multiple
- // interfaces because of PodSelector)
- expectedPrograms, err := rec.expectedBpfPrograms(ctx)
- if err != nil {
- return internal.Requeue, fmt.Errorf("failed to get expected bpfPrograms: %v", err)
- }
-
// Determine if the MapOwnerSelector was set, and if so, see if the MapOwner
// ID can be found.
mapOwnerStatus, err := ProcessMapOwnerParam(ctx, &common.MapOwnerSelector, r)
@@ -302,106 +488,24 @@ func reconcileProgram(ctx context.Context,
"isLoaded", mapOwnerStatus.isLoaded,
"mapOwnerid", mapOwnerStatus.mapOwnerId)
- // multiplex signals into kubernetes API actions
switch isBeingDeleted {
- // Deletion of a *Program takes a few steps if there's existing bpfPrograms:
- // 1. Reconcile the bpfProgram (take bpfman cleanup steps).
- // 2. Remove any finalizers from the bpfProgram Object.
- // 3. Update the condition on the bpfProgram to BpfProgCondUnloaded so the
- // operator knows it's safe to remove the parent Program Object, which
- // is when the bpfProgram is automatically deleted by the owner-reference.
case true:
- for _, prog := range existingPrograms {
- // Reconcile the bpfProgram if error write condition and exit with
- // retry.
- cond, err := rec.reconcileBpfmanProgram(ctx,
- programMap,
- &common.ByteCode,
- &prog,
- isNodeSelected,
- isBeingDeleted,
- mapOwnerStatus,
- )
- if err != nil {
- r.updateStatus(ctx, &prog, cond)
- return internal.Requeue, fmt.Errorf("failed to delete bpfman program: %v", err)
- }
-
- if r.removeFinalizer(ctx, &prog, rec.getFinalizer()) {
- return internal.Updated, nil
- }
-
- if r.updateStatus(ctx, &prog, cond) {
- return internal.Updated, nil
- }
- }
- // If the *Program isn't being deleted ALWAYS create the bpfPrograms
- // even if the node isn't selected
+ return handleProgDelete(ctx, r, common, rec, existingPrograms,
+ programMap, isNodeSelected, isBeingDeleted, mapOwnerStatus)
case false:
- for _, expectedProg := range expectedPrograms.Items {
- prog, exists := existingPrograms[expectedProg.Name]
- if !exists {
- opts := client.CreateOptions{}
- r.Logger.Info("Creating bpfProgram", "Name", expectedProg.Name, "Owner", program.GetName())
- if err := r.Create(ctx, &expectedProg, &opts); err != nil {
- return internal.Requeue, fmt.Errorf("failed to create bpfProgram object: %v", err)
- }
- existingPrograms[expectedProg.Name] = prog
- return internal.Updated, nil
- }
-
- // bpfProgram Object exists go ahead and reconcile it, if there is
- // an error write condition and exit with retry.
- cond, err := rec.reconcileBpfmanProgram(ctx,
- programMap,
- &common.ByteCode,
- &prog,
- isNodeSelected,
- isBeingDeleted,
- mapOwnerStatus,
- )
- if err != nil {
- if r.updateStatus(ctx, &prog, cond) {
- // Return an error the first time.
- return internal.Updated, fmt.Errorf("failed to reconcile bpfman program: %v", err)
- }
- } else {
- // Make sure if we're not selected exit and write correct condition
- if cond == bpfmaniov1alpha1.BpfProgCondNotSelected ||
- cond == bpfmaniov1alpha1.BpfProgCondMapOwnerNotFound ||
- cond == bpfmaniov1alpha1.BpfProgCondMapOwnerNotLoaded {
- // Write NodeNodeSelected status
- if r.updateStatus(ctx, &prog, cond) {
- r.Logger.V(1).Info("Update condition from bpfman reconcile", "condition", cond)
- return internal.Updated, nil
- }
- }
-
- existingId, err := bpfmanagentinternal.GetID(&prog)
- if err != nil {
- return internal.Requeue, fmt.Errorf("failed to get kernel id from bpfProgram: %v", err)
- }
-
- // 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("Updating bpfProgram Object", "Id", r.progId, "bpfProgram", prog.Name)
- // annotations should be populate on create
- prog.Annotations[internal.IdAnnotation] = strconv.FormatUint(uint64(*r.progId), 10)
- if err := r.Update(ctx, &prog, &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, &prog, cond) {
- return internal.Updated, nil
- }
- }
+ // Generate the list of BpfPrograms for this *Program. This handles the
+ // one *Program to many BpfPrograms (e.g., One *Program maps to multiple
+ // interfaces because of PodSelector, or one *Program needs to be
+ // installed in multiple containers because of ContainerSelector).
+ expectedPrograms, err := rec.expectedBpfPrograms(ctx)
+ if err != nil {
+ return internal.Requeue, fmt.Errorf("failed to get expected bpfPrograms: %v", err)
}
+ return handleProgCreateOrUpdate(ctx, r, common, rec, program, existingPrograms,
+ expectedPrograms, programMap, isNodeSelected, isBeingDeleted, mapOwnerStatus)
}
- // We didn't already return something else, so there's nothing to do
- r.Logger.Info("Nothing to do for this program")
+ // This return should never be reached, but it's here to satisfy the compiler.
return internal.Unchanged, nil
}
@@ -485,3 +589,20 @@ func ProcessMapOwnerParam(ctx context.Context,
}
}
}
+
+// get Clientset returns a kubernetes clientset.
+func getClientset() (*kubernetes.Clientset, error) {
+
+ // get the in-cluster config
+ config, err := rest.InClusterConfig()
+ if err != nil {
+ return nil, fmt.Errorf("error getting config: %v", err)
+ }
+ // create the clientset
+ clientset, err := kubernetes.NewForConfig(config)
+ if err != nil {
+ return nil, fmt.Errorf("error creating clientset: %v", err)
+ }
+
+ return clientset, nil
+}
diff --git a/bpfman-operator/controllers/bpfman-agent/containers.go b/bpfman-operator/controllers/bpfman-agent/containers.go
new file mode 100644
index 000000000..fe7ca0167
--- /dev/null
+++ b/bpfman-operator/controllers/bpfman-agent/containers.go
@@ -0,0 +1,196 @@
+/*
+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"
+ "os/exec"
+ "slices"
+ "strconv"
+
+ v1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+
+ bpfmaniov1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1"
+ "github.com/bpfman/bpfman/bpfman-operator/internal"
+ "github.com/buger/jsonparser"
+ "github.com/go-logr/logr"
+)
+
+// getPodsForNode returns a list of pods on the given node that match the given
+// container selector.
+func getPodsForNode(ctx context.Context, clientset kubernetes.Interface,
+ containerSelector *bpfmaniov1alpha1.ContainerSelector, nodeName string) (*v1.PodList, error) {
+
+ selectorString := metav1.FormatLabelSelector(&containerSelector.Pods)
+
+ if selectorString == "" {
+ return nil, fmt.Errorf("error parsing selector: %v", selectorString)
+ }
+
+ listOptions := metav1.ListOptions{
+ FieldSelector: "spec.nodeName=" + nodeName,
+ }
+
+ if selectorString != "" {
+ listOptions.LabelSelector = selectorString
+ }
+
+ podList, err := clientset.CoreV1().Pods(containerSelector.Namespace).List(ctx, listOptions)
+ if err != nil {
+ return nil, fmt.Errorf("error getting pod list: %v", err)
+ }
+
+ return podList, nil
+}
+
+type containerInfo struct {
+ podName string
+ containerName string
+ pid int64
+}
+
+// getContainerInfo returns a list of containerInfo for the given pod list and container names.
+func getContainerInfo(podList *v1.PodList, containerNames *[]string, logger logr.Logger) (*[]containerInfo, error) {
+
+ crictl := "/usr/local/bin/crictl"
+
+ containers := []containerInfo{}
+
+ for i, pod := range podList.Items {
+ logger.V(1).Info("Pod", "index", i, "Name", pod.Name, "Namespace", pod.Namespace, "NodeName", pod.Spec.NodeName)
+
+ // Find the unique Pod ID of the given pod.
+ cmd := exec.Command(crictl, "pods", "--name", pod.Name, "-o", "json")
+ podInfo, err := cmd.Output()
+ if err != nil {
+ logger.Info("Failed to get pod info", "error", err)
+ return nil, err
+ }
+
+ // The crictl --name option works like a grep on the names of pods.
+ // Since we are using the unique name of the pod generated by k8s, we
+ // will most likely only get one pod. Though very unlikely, it is
+ // technically possible that this unique name is a substring of another
+ // pod name. If that happens, we would get multiple pods, so we handle
+ // that possibility with the following for loop.
+ var podId string
+ podFound := false
+ for podIndex := 0; ; podIndex++ {
+ indexString := "[" + strconv.Itoa(podIndex) + "]"
+ podId, err = jsonparser.GetString(podInfo, "items", indexString, "id")
+ if err != nil {
+ // We hit the end of the list of pods and didn't find it. This
+ // should only happen if the pod was deleted between the time we
+ // got the list of pods and the time we got the info about the
+ // pod.
+ break
+ }
+ podName, err := jsonparser.GetString(podInfo, "items", indexString, "metadata", "name")
+ if err != nil {
+ // We shouldn't get an error here if we didn't get an error
+ // above, but just in case...
+ logger.Error(err, "Error getting pod name")
+ break
+ }
+
+ if podName == pod.Name {
+ podFound = true
+ break
+ }
+ }
+
+ if !podFound {
+ return nil, fmt.Errorf("pod %s not found in crictl pod list", pod.Name)
+ }
+
+ logger.V(1).Info("podFound", "podId", podId, "err", err)
+
+ // Get info about the containers in the pod so we can get their unique IDs.
+ cmd = exec.Command(crictl, "ps", "--pod", podId, "-o", "json")
+ containerData, err := cmd.Output()
+ if err != nil {
+ logger.Info("Failed to get container info", "error", err)
+ return nil, err
+ }
+
+ // For each container in the pod...
+ for containerIndex := 0; ; containerIndex++ {
+
+ indexString := "[" + strconv.Itoa(containerIndex) + "]"
+
+ // Make sure the container name is in the list of containers we want.
+ containerName, err := jsonparser.GetString(containerData, "containers", indexString, "metadata", "name")
+ if err != nil {
+ break
+ }
+
+ if containerNames != nil &&
+ len(*containerNames) > 0 &&
+ !slices.Contains((*containerNames), containerName) {
+ continue
+ }
+
+ // If it is in the list, get the container ID.
+ containerId, err := jsonparser.GetString(containerData, "containers", indexString, "id")
+ if err != nil {
+ break
+ }
+
+ // Now use the container ID to get more info about the container so
+ // we can get the PID.
+ cmd = exec.Command(crictl, "inspect", "-o", "json", containerId)
+ containerData, err := cmd.Output()
+ if err != nil {
+ logger.Info("Failed to get container data", "error", err)
+ continue
+ }
+ containerPid, err := jsonparser.GetInt(containerData, "info", "pid")
+ if err != nil {
+ logger.Info("Failed to get container PID", "error", err)
+ continue
+ }
+
+ container := containerInfo{
+ podName: pod.Name,
+ containerName: containerName,
+ pid: containerPid,
+ }
+
+ containers = append(containers, container)
+ }
+
+ }
+
+ return &containers, nil
+}
+
+// Check if the annotation is set to indicate that no containers on this node
+// matched the container selector.
+func noContainersOnNode(bpfProgram *bpfmaniov1alpha1.BpfProgram) bool {
+ if bpfProgram == nil {
+ return false
+ }
+
+ noContainersOnNode, ok := bpfProgram.Annotations[internal.UprobeNoContainersOnNode]
+ if ok && noContainersOnNode == "true" {
+ return true
+ }
+
+ return false
+}
diff --git a/bpfman-operator/controllers/bpfman-agent/discovered_program.go b/bpfman-operator/controllers/bpfman-agent/discovered_program.go
deleted file mode 100644
index 8f4383cb9..000000000
--- a/bpfman-operator/controllers/bpfman-agent/discovered_program.go
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
-Copyright 2023.
-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"
- "reflect"
- "strings"
- "time"
-
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- ctrl "sigs.k8s.io/controller-runtime"
- "sigs.k8s.io/controller-runtime/pkg/builder"
- "sigs.k8s.io/controller-runtime/pkg/client"
- "sigs.k8s.io/controller-runtime/pkg/handler"
- "sigs.k8s.io/controller-runtime/pkg/log"
- "sigs.k8s.io/controller-runtime/pkg/predicate"
- "sigs.k8s.io/controller-runtime/pkg/source"
-
- bpfmaniov1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1"
- bpfmanagentinternal "github.com/bpfman/bpfman/bpfman-operator/controllers/bpfman-agent/internal"
- "github.com/bpfman/bpfman/bpfman-operator/internal"
- v1 "k8s.io/api/core/v1"
-)
-
-const (
- syncDurationDiscoveredController = 30 * time.Second
-)
-
-type DiscoveredProgramReconciler struct {
- ReconcilerCommon
-}
-
-// SetupWithManager sets up the controller with the Manager.
-func (r *DiscoveredProgramReconciler) SetupWithManager(mgr ctrl.Manager) error {
- return ctrl.NewControllerManagedBy(mgr).
- Named("DiscoveredProgramController").
- // Only trigger reconciliation if node object changed. Additionally always
- // exit the end of a reconcile with a requeue so that the discovered programs
- // are kept up to date.
- Watches(
- &source.Kind{Type: &v1.Node{}},
- &handler.EnqueueRequestForObject{},
- builder.WithPredicates(predicate.And(predicate.ResourceVersionChangedPredicate{}, nodePredicate(r.NodeName))),
- ).
- Watches(
- &source.Kind{Type: &bpfmaniov1alpha1.BpfProgram{}},
- &handler.EnqueueRequestForObject{},
- builder.WithPredicates(predicate.And(
- internal.BpfProgramNodePredicate(r.NodeName)),
- internal.DiscoveredBpfProgramPredicate(),
- ),
- ).
- Complete(r)
-}
-
-// Reconcile ALL discovered bpf programs on the system whenever an event is received.
-func (r *DiscoveredProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
- r.Logger = ctrl.Log.WithName("discvr")
-
- // The Discovery Reconciler gets called more than others so this tends to be a noisy log,
- // so moved to a Trace log.
- ctxLogger := log.FromContext(ctx)
- ctxLogger.V(2).Info("Reconcile DiscoveredProgs: Enter", "ReconcileKey", req)
-
- // Get existing ebpf state from bpfman.
- programs, err := bpfmanagentinternal.ListAllPrograms(ctx, r.BpfmanClient)
- if err != nil {
- r.Logger.Error(err, "failed to list loaded bpf programs")
- return ctrl.Result{Requeue: true, RequeueAfter: retryDurationAgent}, nil
- }
-
- // get all existing "discovered" bpfProgram Objects for this node
- opts := []client.ListOption{
- client.MatchingLabels{internal.DiscoveredLabel: "", internal.K8sHostLabel: r.NodeName},
- }
-
- existingPrograms := bpfmaniov1alpha1.BpfProgramList{}
- err = r.Client.List(ctx, &existingPrograms, opts...)
- if err != nil {
- r.Logger.Error(err, "failed to list existing discovered bpfProgram objects")
- }
-
- // build an indexable map of existing programs based on bpfProgram name
- existingProgramIndex := map[string]bpfmaniov1alpha1.BpfProgram{}
- for _, p := range existingPrograms.Items {
- existingProgramIndex[p.Name] = p
- }
-
- for _, p := range programs {
- kernelInfo := p.GetKernelInfo()
- if kernelInfo == nil {
- continue
- }
-
- programInfo := p.GetInfo()
- if programInfo != nil {
- // skip bpf programs loaded by bpfman, their corresponding bpfProgram object
- // will be managed by another controller.
- metadata := programInfo.GetMetadata()
- if _, ok := metadata[internal.UuidMetadataKey]; ok {
- continue
- }
- }
-
- // TODO(astoycos) across the agent we need a better way to validate that
- // the object name we're building is a valid k8's object name i.e meets the
- // regex: '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*'
- // laid out here -> https://github.com/kubernetes/apimachinery/blob/v0.27.4/pkg/util/validation/validation.go#L43C6-L43C21
- bpfProgName := ""
- if len(kernelInfo.Name) == 0 {
- bpfProgName = fmt.Sprintf("%d-%s", kernelInfo.Id, r.NodeName)
- } else {
- bpfProgName = fmt.Sprintf("%s-%d-%s", strings.ReplaceAll(kernelInfo.Name, "_", "-"), kernelInfo.Id, r.NodeName)
- }
-
- expectedBpfProg := &bpfmaniov1alpha1.BpfProgram{
- ObjectMeta: metav1.ObjectMeta{
- Name: bpfProgName,
- Labels: map[string]string{internal.DiscoveredLabel: "",
- internal.K8sHostLabel: r.NodeName},
- Annotations: bpfmanagentinternal.Build_kernel_info_annotations(p),
- },
- Spec: bpfmaniov1alpha1.BpfProgramSpec{
- Type: internal.ProgramType(kernelInfo.ProgramType).String(),
- },
- Status: bpfmaniov1alpha1.BpfProgramStatus{Conditions: []metav1.Condition{}},
- }
-
- existingBpfProg, ok := existingProgramIndex[bpfProgName]
- // If the bpfProgram object doesn't exist create it.
- if !ok {
- r.Logger.Info("Creating discovered bpfProgram object", "name", expectedBpfProg.Name)
- err = r.Create(ctx, expectedBpfProg)
- if err != nil {
- r.Logger.Error(err, "failed to create bpfProgram object")
- }
-
- return ctrl.Result{Requeue: false}, nil
- }
-
- // If the bpfProgram object does exist but is stale update it.
- if !reflect.DeepEqual(expectedBpfProg.Annotations, existingBpfProg.Annotations) {
- if err := r.Update(ctx, expectedBpfProg); err != nil {
- r.Logger.Error(err, "failed to update discovered bpfProgram object", "name", expectedBpfProg.Name)
- }
-
- return ctrl.Result{Requeue: false}, nil
- }
-
- delete(existingProgramIndex, bpfProgName)
- }
-
- // Delete any stale discovered programs
- for _, prog := range existingProgramIndex {
- r.Logger.Info("Deleting stale discovered bpfProgram object", "name", prog.Name)
- if err := r.Delete(ctx, &prog, &client.DeleteOptions{}); err != nil {
- r.Logger.Error(err, "failed to delete stale discoverd bpfProgram object", "name", prog.Name)
- }
-
- return ctrl.Result{Requeue: false}, nil
- }
-
- // If we've finished reconciling everything, make sure to exit with a retry
- // so that we resync on a 30 second interval.
- return ctrl.Result{Requeue: true, RequeueAfter: syncDurationDiscoveredController}, nil
-}
diff --git a/bpfman-operator/controllers/bpfman-agent/discovered_program_test.go b/bpfman-operator/controllers/bpfman-agent/discovered_program_test.go
deleted file mode 100644
index a0624fca0..000000000
--- a/bpfman-operator/controllers/bpfman-agent/discovered_program_test.go
+++ /dev/null
@@ -1,424 +0,0 @@
-/*
-Copyright 2023.
-
-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"
-
- bpfmaniov1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1"
- bpfmanagentinternal "github.com/bpfman/bpfman/bpfman-operator/controllers/bpfman-agent/internal"
- agenttestutils "github.com/bpfman/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils"
- "github.com/bpfman/bpfman/bpfman-operator/internal"
- testutils "github.com/bpfman/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 TestDiscoveredProgramControllerCreate(t *testing.T) {
- var (
- namespace = "bpfman"
- fakeNode = testutils.NewNode("fake-control-plane")
- ctx = context.TODO()
- bpfProgName0 = fmt.Sprintf("%s-%d-%s", "dump-bpf-map", 693, fakeNode.Name)
- bpfProgName1 = fmt.Sprintf("%s-%d-%s", "dump-bpf-prog", 694, fakeNode.Name)
- bpfProgName2 = fmt.Sprintf("%d-%s", 695, fakeNode.Name)
- bpfProg = &bpfmaniov1alpha1.BpfProgram{}
- fakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81"
- programs = map[int]*gobpfman.ListResponse_ListResult{
- 693: {
- KernelInfo: &gobpfman.KernelProgramInfo{
- Name: "dump_bpf_map",
- ProgramType: 26,
- Id: 693,
- LoadedAt: "2023-03-02T18:15:06+0000",
- Tag: "749172daffada61f",
- GplCompatible: true,
- MapIds: []uint32{45},
- BtfId: 154,
- BytesXlated: 264,
- Jited: true,
- BytesJited: 287,
- BytesMemlock: 4096,
- VerifiedInsns: 34,
- },
- },
- 694: {
- KernelInfo: &gobpfman.KernelProgramInfo{
- Name: "dump_bpf_prog",
- ProgramType: 26,
- Id: 694,
- LoadedAt: "2023-03-02T18:15:06+0000",
- Tag: "bc36dd738319ea32",
- GplCompatible: true,
- MapIds: []uint32{45},
- BtfId: 154,
- BytesXlated: 528,
- Jited: true,
- BytesJited: 715,
- BytesMemlock: 4096,
- VerifiedInsns: 84,
- },
- },
- // test program with no name
- 695: {
- KernelInfo: &gobpfman.KernelProgramInfo{
- ProgramType: 8,
- Id: 695,
- LoadedAt: "2023-07-20T19:11:11+0000",
- Tag: "6deef7357e7b4530",
- GplCompatible: true,
- BytesXlated: 64,
- Jited: true,
- BytesJited: 55,
- BytesMemlock: 4096,
- VerifiedInsns: 8,
- },
- },
- // skip program loaded by bpfman
- 696: {
- Info: &gobpfman.ProgramInfo{
- Metadata: map[string]string{internal.UuidMetadataKey: fakeUID},
- },
- KernelInfo: &gobpfman.KernelProgramInfo{
- ProgramType: 8,
- Id: 696,
- LoadedAt: "2023-07-20T19:11:11+0000",
- Tag: "6deef7357e7b4530",
- GplCompatible: true,
- BytesXlated: 64,
- Jited: true,
- BytesJited: 55,
- BytesMemlock: 4096,
- VerifiedInsns: 8,
- },
- },
- }
- )
-
- // Objects to track in the fake client.
- objs := []runtime.Object{fakeNode}
-
- // Register operator types with the runtime scheme.
- s := scheme.Scheme
- s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgram{})
- s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgramList{})
-
- // Create a fake client to mock API calls.
- cl := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build()
-
- cli := agenttestutils.NewBpfmanClientFakeWithPrograms(programs)
-
- rc := ReconcilerCommon{
- Client: cl,
- Scheme: s,
- BpfmanClient: cli,
- NodeName: fakeNode.Name,
- }
-
- // 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 := &DiscoveredProgramReconciler{ReconcilerCommon: rc}
-
- // Mock request to simulate Reconcile() being called on an event for a
- // watched resource .
- req := reconcile.Request{
- NamespacedName: types.NamespacedName{
- Name: "fake-control-plane",
- Namespace: namespace,
- },
- }
-
- // Three reconciles should create three bpf program objects
- res, err := r.Reconcile(ctx, req)
- if err != nil {
- t.Fatalf("reconcile: (%v)", err)
- }
-
- require.False(t, res.Requeue)
-
- res, err = r.Reconcile(ctx, req)
- if err != nil {
- t.Fatalf("reconcile: (%v)", err)
- }
-
- require.False(t, res.Requeue)
-
- res, err = r.Reconcile(ctx, req)
- if err != nil {
- t.Fatalf("reconcile: (%v)", err)
- }
-
- require.False(t, res.Requeue)
-
- // Check the first discovered BpfProgram Object was created successfully
- err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName0, Namespace: metav1.NamespaceAll}, bpfProg)
- require.NoError(t, err)
-
- require.NotEmpty(t, bpfProg)
- // discovered Label is written
- require.Contains(t, bpfProg.Labels, internal.DiscoveredLabel)
- // node Label was correctly set
- require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name)
- // ensure annotations were correct
- require.Equal(t, bpfProg.Annotations, bpfmanagentinternal.Build_kernel_info_annotations(programs[693]))
-
- // Check the second discovered BpfProgram Object was created successfully
- err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName1, Namespace: metav1.NamespaceAll}, bpfProg)
- require.NoError(t, err)
-
- require.NotEmpty(t, bpfProg)
- // discovered Label is written
- require.Contains(t, bpfProg.Labels, internal.DiscoveredLabel)
- // node Label was correctly set
- require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name)
- // ensure annotations were correct
- require.Equal(t, bpfProg.Annotations, bpfmanagentinternal.Build_kernel_info_annotations(programs[694]))
-
- // Check the third discovered BpfProgram Object was created successfully
- err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName2, Namespace: metav1.NamespaceAll}, bpfProg)
- require.NoError(t, err)
-
- require.NotEmpty(t, bpfProg)
- // discovered Label is written
- require.Contains(t, bpfProg.Labels, internal.DiscoveredLabel)
- // node Label was correctly set
- require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name)
- // ensure annotations were correct
- require.Equal(t, bpfProg.Annotations, bpfmanagentinternal.Build_kernel_info_annotations(programs[695]))
-
- // The fourth reconcile will end up exiting with a 30 second requeue
- res, err = r.Reconcile(ctx, req)
- if err != nil {
- t.Fatalf("reconcile: (%v)", err)
- }
-
- // Require requeue
- require.True(t, res.Requeue)
-}
-
-func TestDiscoveredProgramControllerCreateAndDeleteStale(t *testing.T) {
- var (
- namespace = "bpfman"
- fakeNode = testutils.NewNode("fake-control-plane")
- ctx = context.TODO()
- bpfProgName0 = fmt.Sprintf("%s-%d-%s", "dump-bpf-map", 693, fakeNode.Name)
- bpfProgName1 = fmt.Sprintf("%s-%d-%s", "dump-bpf-prog", 694, fakeNode.Name)
- bpfProgName2 = fmt.Sprintf("%d-%s", 695, fakeNode.Name)
- bpfProg = &bpfmaniov1alpha1.BpfProgram{}
- fakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81"
- programs = map[int]*gobpfman.ListResponse_ListResult{
- 693: {
- KernelInfo: &gobpfman.KernelProgramInfo{
- Name: "dump_bpf_map",
- ProgramType: 26,
- Id: 693,
- LoadedAt: "2023-03-02T18:15:06+0000",
- Tag: "749172daffada61f",
- GplCompatible: true,
- MapIds: []uint32{45},
- BtfId: 154,
- BytesXlated: 264,
- Jited: true,
- BytesJited: 287,
- BytesMemlock: 4096,
- VerifiedInsns: 34,
- },
- },
- 694: {
- KernelInfo: &gobpfman.KernelProgramInfo{
- Name: "dump_bpf_prog",
- ProgramType: 26,
- Id: 694,
- LoadedAt: "2023-03-02T18:15:06+0000",
- Tag: "bc36dd738319ea32",
- GplCompatible: true,
- MapIds: []uint32{45},
- BtfId: 154,
- BytesXlated: 528,
- Jited: true,
- BytesJited: 715,
- BytesMemlock: 4096,
- VerifiedInsns: 84,
- },
- },
- // test program with no name
- 695: {
- KernelInfo: &gobpfman.KernelProgramInfo{
- ProgramType: 8,
- Id: 695,
- LoadedAt: "2023-07-20T19:11:11+0000",
- Tag: "6deef7357e7b4530",
- GplCompatible: true,
- BytesXlated: 64,
- Jited: true,
- BytesJited: 55,
- BytesMemlock: 4096,
- VerifiedInsns: 8,
- },
- },
- // skip program loaded by bpfman
- 696: {
- Info: &gobpfman.ProgramInfo{
- Metadata: map[string]string{internal.UuidMetadataKey: fakeUID},
- },
- KernelInfo: &gobpfman.KernelProgramInfo{
- ProgramType: 8,
- Id: 696,
- LoadedAt: "2023-07-20T19:11:11+0000",
- Tag: "6deef7357e7b4530",
- GplCompatible: true,
- BytesXlated: 64,
- Jited: true,
- BytesJited: 55,
- BytesMemlock: 4096,
- VerifiedInsns: 8,
- },
- },
- }
- )
-
- // Objects to track in the fake client.
- objs := []runtime.Object{fakeNode}
-
- // Register operator types with the runtime scheme.
- s := scheme.Scheme
- s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgram{})
- s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgramList{})
-
- // Create a fake client to mock API calls.
- cl := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build()
-
- cli := agenttestutils.NewBpfmanClientFakeWithPrograms(programs)
-
- rc := ReconcilerCommon{
- Client: cl,
- Scheme: s,
- BpfmanClient: cli,
- NodeName: fakeNode.Name,
- }
-
- // 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 := &DiscoveredProgramReconciler{ReconcilerCommon: rc}
-
- // Mock request to simulate Reconcile() being called on an event for a
- // watched resource .
- req := reconcile.Request{
- NamespacedName: types.NamespacedName{
- Name: "fake-control-plane",
- Namespace: namespace,
- },
- }
-
- // Three reconciles should create three bpf program objects
- res, err := r.Reconcile(ctx, req)
- if err != nil {
- t.Fatalf("reconcile: (%v)", err)
- }
-
- require.False(t, res.Requeue)
-
- res, err = r.Reconcile(ctx, req)
- if err != nil {
- t.Fatalf("reconcile: (%v)", err)
- }
-
- require.False(t, res.Requeue)
-
- res, err = r.Reconcile(ctx, req)
- if err != nil {
- t.Fatalf("reconcile: (%v)", err)
- }
-
- require.False(t, res.Requeue)
-
- // Check the first discovered BpfProgram Object was created successfully
- err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName0, Namespace: metav1.NamespaceAll}, bpfProg)
- require.NoError(t, err)
-
- require.NotEmpty(t, bpfProg)
- // discovered Label is written
- require.Contains(t, bpfProg.Labels, internal.DiscoveredLabel)
- // node Label was correctly set
- require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name)
- // ensure annotations were correct
- require.Equal(t, bpfProg.Annotations, bpfmanagentinternal.Build_kernel_info_annotations(programs[693]))
-
- // Check the second discovered BpfProgram Object was created successfully
- err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName1, Namespace: metav1.NamespaceAll}, bpfProg)
- require.NoError(t, err)
-
- require.NotEmpty(t, bpfProg)
- // discovered Label is written
- require.Contains(t, bpfProg.Labels, internal.DiscoveredLabel)
- // node Label was correctly set
- require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name)
- // ensure annotations were correct
- require.Equal(t, bpfProg.Annotations, bpfmanagentinternal.Build_kernel_info_annotations(programs[694]))
-
- // Check the third discovered BpfProgram Object was created successfully
- err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName2, Namespace: metav1.NamespaceAll}, bpfProg)
- require.NoError(t, err)
-
- require.NotEmpty(t, bpfProg)
- // discovered Label is written
- require.Contains(t, bpfProg.Labels, internal.DiscoveredLabel)
- // node Label was correctly set
- require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name)
- // ensure annotations were correct
- require.Equal(t, bpfProg.Annotations, bpfmanagentinternal.Build_kernel_info_annotations(programs[695]))
-
- // delete program
- _, err = rc.BpfmanClient.Unload(ctx, &gobpfman.UnloadRequest{Id: 693})
- require.NoError(t, err)
-
- // The fourth reconcile will end up deleting the extra bpfProgram
- res, err = r.Reconcile(ctx, req)
- if err != nil {
- t.Fatalf("reconcile: (%v)", err)
- }
-
- require.False(t, res.Requeue)
-
- // Check the first discovered BpfProgram Object was deleted successfully
- err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName0, Namespace: metav1.NamespaceAll}, bpfProg)
- require.Error(t, err)
-
- // When all work is done make sure we will reconcile again soon.
- res, err = r.Reconcile(ctx, req)
- if err != nil {
- t.Fatalf("reconcile: (%v)", err)
- }
-
- require.True(t, res.Requeue)
-}
diff --git a/bpfman-operator/controllers/bpfman-agent/fentry-program.go b/bpfman-operator/controllers/bpfman-agent/fentry-program.go
new file mode 100644
index 000000000..2f270a7d8
--- /dev/null
+++ b/bpfman-operator/controllers/bpfman-agent/fentry-program.go
@@ -0,0 +1,329 @@
+/*
+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/bpfman-operator/apis/v1alpha1"
+ bpfmanagentinternal "github.com/bpfman/bpfman/bpfman-operator/controllers/bpfman-agent/internal"
+ "github.com/bpfman/bpfman/bpfman-operator/internal"
+ gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1"
+
+ v1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/types"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/builder"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/handler"
+ "sigs.k8s.io/controller-runtime/pkg/log"
+ "sigs.k8s.io/controller-runtime/pkg/predicate"
+ "sigs.k8s.io/controller-runtime/pkg/source"
+)
+
+//+kubebuilder:rbac:groups=bpfman.io,resources=fentryprograms,verbs=get;list;watch
+
+// BpfProgramReconciler reconciles a BpfProgram object
+type FentryProgramReconciler struct {
+ ReconcilerCommon
+ currentFentryProgram *bpfmaniov1alpha1.FentryProgram
+ ourNode *v1.Node
+}
+
+func (r *FentryProgramReconciler) getRecCommon() *ReconcilerCommon {
+ return &r.ReconcilerCommon
+}
+
+func (r *FentryProgramReconciler) getFinalizer() string {
+ return internal.FentryProgramControllerFinalizer
+}
+
+func (r *FentryProgramReconciler) getRecType() string {
+ return internal.Tracing.String()
+}
+
+// SetupWithManager sets up the controller with the Manager.
+// The Bpfman-Agent should reconcile whenever a FentryProgram is updated,
+// load the program to the node via bpfman, and then create a bpfProgram object
+// to reflect per node state information.
+func (r *FentryProgramReconciler) SetupWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&bpfmaniov1alpha1.FentryProgram{}, builder.WithPredicates(predicate.And(predicate.GenerationChangedPredicate{}, predicate.ResourceVersionChangedPredicate{}))).
+ Owns(&bpfmaniov1alpha1.BpfProgram{},
+ builder.WithPredicates(predicate.And(
+ internal.BpfProgramTypePredicate(internal.Tracing.String()),
+ internal.BpfProgramNodePredicate(r.NodeName)),
+ ),
+ ).
+ // Only trigger reconciliation if node labels change since that could
+ // make the FentryProgram no longer select the Node. Additionally only
+ // care about node events specific to our node
+ Watches(
+ &source.Kind{Type: &v1.Node{}},
+ &handler.EnqueueRequestForObject{},
+ builder.WithPredicates(predicate.And(predicate.LabelChangedPredicate{}, nodePredicate(r.NodeName))),
+ ).
+ Complete(r)
+}
+
+func (r *FentryProgramReconciler) expectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfProgramList, error) {
+ progs := &bpfmaniov1alpha1.BpfProgramList{}
+
+ // sanitize fentry name to work in a bpfProgram name
+ sanatizedFentry := strings.Replace(strings.Replace(r.currentFentryProgram.Spec.FunctionName, "/", "-", -1), "_", "-", -1)
+ bpfProgramName := fmt.Sprintf("%s-%s-%s", r.currentFentryProgram.Name, r.NodeName, sanatizedFentry)
+
+ annotations := map[string]string{internal.FentryProgramFunction: r.currentFentryProgram.Spec.FunctionName}
+
+ prog, err := r.createBpfProgram(ctx, bpfProgramName, r.getFinalizer(), r.currentFentryProgram, r.getRecType(), annotations)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err)
+ }
+
+ progs.Items = append(progs.Items, *prog)
+
+ return progs, nil
+}
+
+func (r *FentryProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ // Initialize node and current program
+ r.currentFentryProgram = &bpfmaniov1alpha1.FentryProgram{}
+ r.ourNode = &v1.Node{}
+ r.Logger = ctrl.Log.WithName("fentry")
+
+ ctxLogger := log.FromContext(ctx)
+ ctxLogger.Info("Reconcile Fentry: Enter", "ReconcileKey", req)
+
+ // Lookup K8s node object for this bpfman-agent This should always succeed
+ if err := r.Get(ctx, types.NamespacedName{Namespace: v1.NamespaceAll, Name: r.NodeName}, r.ourNode); err != nil {
+ return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfman-agent node %s : %v",
+ req.NamespacedName, err)
+ }
+
+ fentryPrograms := &bpfmaniov1alpha1.FentryProgramList{}
+
+ opts := []client.ListOption{}
+
+ if err := r.List(ctx, fentryPrograms, opts...); err != nil {
+ return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting FentryPrograms for full reconcile %s : %v",
+ req.NamespacedName, err)
+ }
+
+ if len(fentryPrograms.Items) == 0 {
+ r.Logger.Info("FentryProgramController found no Fentry Programs")
+ return ctrl.Result{Requeue: false}, nil
+ }
+
+ // Get existing ebpf state from bpfman.
+ programMap, err := bpfmanagentinternal.ListBpfmanPrograms(ctx, r.BpfmanClient, internal.Tracing)
+ if err != nil {
+ r.Logger.Error(err, "failed to list loaded bpfman programs")
+ return ctrl.Result{Requeue: true, RequeueAfter: retryDurationAgent}, nil
+ }
+
+ // Reconcile each FentryProgram. Don't return error here because it will trigger an infinite reconcile loop, instead
+ // report the error to user and retry if specified. For some errors the controller may not decide to retry.
+ // Note: This only results in grpc calls to bpfman if we need to change something
+ requeue := false // initialize requeue to false
+ for _, fentryProgram := range fentryPrograms.Items {
+ r.Logger.Info("FentryProgramController is reconciling", "currentFentryProgram", fentryProgram.Name)
+ r.currentFentryProgram = &fentryProgram
+ result, err := reconcileProgram(ctx, r, r.currentFentryProgram, &r.currentFentryProgram.Spec.BpfProgramCommon, r.ourNode, programMap)
+ if err != nil {
+ r.Logger.Error(err, "Reconciling FentryProgram Failed", "FentryProgramName", r.currentFentryProgram.Name, "ReconcileResult", result.String())
+ }
+
+ switch result {
+ case internal.Unchanged:
+ // continue with next program
+ case internal.Updated:
+ // return
+ return ctrl.Result{Requeue: false}, nil
+ case internal.Requeue:
+ // remember to do a requeue when we're done and continue with next program
+ requeue = true
+ }
+ }
+
+ if requeue {
+ // A requeue has been requested
+ return ctrl.Result{RequeueAfter: retryDurationAgent}, nil
+ } else {
+ // We've made it through all the programs in the list without anything being
+ // updated and a reque has not been requested.
+ return ctrl.Result{Requeue: false}, nil
+ }
+}
+
+func (r *FentryProgramReconciler) buildFentryLoadRequest(
+ bytecode *gobpfman.BytecodeLocation,
+ uuid string,
+ bpfProgram *bpfmaniov1alpha1.BpfProgram,
+ mapOwnerId *uint32) *gobpfman.LoadRequest {
+
+ return &gobpfman.LoadRequest{
+ Bytecode: bytecode,
+ Name: r.currentFentryProgram.Spec.BpfFunctionName,
+ ProgramType: uint32(internal.Tracing),
+ Attach: &gobpfman.AttachInfo{
+ Info: &gobpfman.AttachInfo_FentryAttachInfo{
+ FentryAttachInfo: &gobpfman.FentryAttachInfo{
+ FnName: bpfProgram.Annotations[internal.FentryProgramFunction],
+ },
+ },
+ },
+ Metadata: map[string]string{internal.UuidMetadataKey: uuid, internal.ProgramNameKey: r.currentFentryProgram.Name},
+ GlobalData: r.currentFentryProgram.Spec.GlobalData,
+ MapOwnerId: mapOwnerId,
+ }
+}
+
+// reconcileBpfmanPrograms ONLY reconciles the bpfman state for a single BpfProgram.
+// It does not interact with the k8s API in any way.
+func (r *FentryProgramReconciler) reconcileBpfmanProgram(ctx context.Context,
+ existingBpfPrograms map[string]*gobpfman.ListResponse_ListResult,
+ bytecodeSelector *bpfmaniov1alpha1.BytecodeSelector,
+ bpfProgram *bpfmaniov1alpha1.BpfProgram,
+ isNodeSelected bool,
+ isBeingDeleted bool,
+ mapOwnerStatus *MapOwnerParamStatus) (bpfmaniov1alpha1.BpfProgramConditionType, error) {
+
+ r.Logger.V(1).Info("Existing bpfProgram", "UUID", bpfProgram.UID, "Name", bpfProgram.Name, "CurrentFentryProgram", r.currentFentryProgram.Name)
+
+ uuid := bpfProgram.UID
+
+ getLoadRequest := func() (*gobpfman.LoadRequest, bpfmaniov1alpha1.BpfProgramConditionType, error) {
+ bytecode, err := bpfmanagentinternal.GetBytecode(r.Client, bytecodeSelector)
+ if err != nil {
+ return nil, bpfmaniov1alpha1.BpfProgCondBytecodeSelectorError, fmt.Errorf("failed to process bytecode selector: %v", err)
+ }
+ loadRequest := r.buildFentryLoadRequest(bytecode, string(uuid), bpfProgram, mapOwnerStatus.mapOwnerId)
+ return loadRequest, bpfmaniov1alpha1.BpfProgCondNone, nil
+ }
+
+ existingProgram, doesProgramExist := existingBpfPrograms[string(uuid)]
+ if !doesProgramExist {
+ r.Logger.V(1).Info("FentryProgram doesn't exist on node")
+
+ // If FentryProgram is being deleted just exit
+ if isBeingDeleted {
+ return bpfmaniov1alpha1.BpfProgCondUnloaded, nil
+ }
+
+ // Make sure if we're not selected just exit
+ if !isNodeSelected {
+ return bpfmaniov1alpha1.BpfProgCondNotSelected, nil
+ }
+
+ // Make sure if the Map Owner is set but not found then just exit
+ if mapOwnerStatus.isSet && !mapOwnerStatus.isFound {
+ return bpfmaniov1alpha1.BpfProgCondMapOwnerNotFound, nil
+ }
+
+ // Make sure if the Map Owner is set but not loaded then just exit
+ if mapOwnerStatus.isSet && !mapOwnerStatus.isLoaded {
+ return bpfmaniov1alpha1.BpfProgCondMapOwnerNotLoaded, nil
+ }
+
+ // otherwise load it
+ loadRequest, condition, err := getLoadRequest()
+ if err != nil {
+ return condition, err
+ }
+
+ r.progId, err = bpfmanagentinternal.LoadBpfmanProgram(ctx, r.BpfmanClient, loadRequest)
+ if err != nil {
+ r.Logger.Error(err, "Failed to load FentryProgram")
+ return bpfmaniov1alpha1.BpfProgCondNotLoaded, nil
+ }
+
+ r.Logger.Info("bpfman called to load FentryProgram on Node", "Name", bpfProgram.Name, "UUID", uuid)
+ return bpfmaniov1alpha1.BpfProgCondLoaded, nil
+ }
+
+ // prog ID should already have been set if program exists
+ id, err := bpfmanagentinternal.GetID(bpfProgram)
+ if err != nil {
+ r.Logger.Error(err, "Failed to get program ID")
+ return bpfmaniov1alpha1.BpfProgCondNotLoaded, nil
+ }
+
+ // BpfProgram exists but either FentryProgram is being deleted, node is no
+ // longer selected, or map is not available....unload program
+ if isBeingDeleted || !isNodeSelected ||
+ (mapOwnerStatus.isSet && (!mapOwnerStatus.isFound || !mapOwnerStatus.isLoaded)) {
+ r.Logger.V(1).Info("FentryProgram exists on Node but is scheduled for deletion, not selected, or map not available",
+ "isDeleted", isBeingDeleted, "isSelected", isNodeSelected, "mapIsSet", mapOwnerStatus.isSet,
+ "mapIsFound", mapOwnerStatus.isFound, "mapIsLoaded", mapOwnerStatus.isLoaded, "id", id)
+
+ if err := bpfmanagentinternal.UnloadBpfmanProgram(ctx, r.BpfmanClient, *id); err != nil {
+ r.Logger.Error(err, "Failed to unload FentryProgram")
+ return bpfmaniov1alpha1.BpfProgCondNotUnloaded, nil
+ }
+
+ r.Logger.Info("bpfman called to unload FentryProgram on Node", "Name", bpfProgram.Name, "UUID", uuid)
+
+ if isBeingDeleted {
+ return bpfmaniov1alpha1.BpfProgCondUnloaded, nil
+ }
+
+ if !isNodeSelected {
+ return bpfmaniov1alpha1.BpfProgCondNotSelected, nil
+ }
+
+ if mapOwnerStatus.isSet && !mapOwnerStatus.isFound {
+ return bpfmaniov1alpha1.BpfProgCondMapOwnerNotFound, nil
+ }
+
+ if mapOwnerStatus.isSet && !mapOwnerStatus.isLoaded {
+ return bpfmaniov1alpha1.BpfProgCondMapOwnerNotLoaded, nil
+ }
+ }
+
+ // BpfProgram exists but is not correct state, unload and recreate
+ loadRequest, condition, err := getLoadRequest()
+ if err != nil {
+ return condition, err
+ }
+
+ r.Logger.V(1).WithValues("expectedProgram", loadRequest).WithValues("existingProgram", existingProgram).Info("StateMatch")
+
+ isSame, reasons := bpfmanagentinternal.DoesProgExist(existingProgram, loadRequest)
+ if !isSame {
+ r.Logger.V(1).Info("FentryProgram is in wrong state, unloading and reloading", "Reason", reasons)
+ if err := bpfmanagentinternal.UnloadBpfmanProgram(ctx, r.BpfmanClient, *id); err != nil {
+ r.Logger.Error(err, "Failed to unload FentryProgram")
+ return bpfmaniov1alpha1.BpfProgCondNotUnloaded, nil
+ }
+
+ r.progId, err = bpfmanagentinternal.LoadBpfmanProgram(ctx, r.BpfmanClient, loadRequest)
+ if err != nil {
+ r.Logger.Error(err, "Failed to load FentryProgram")
+ return bpfmaniov1alpha1.BpfProgCondNotLoaded, err
+ }
+
+ r.Logger.Info("bpfman called to reload FentryProgram on Node", "Name", bpfProgram.Name, "UUID", uuid)
+ } else {
+ // Program exists and bpfProgram K8s Object is up to date
+ r.Logger.V(1).Info("Ignoring Object Change nothing to do in bpfman")
+ r.progId = id
+ }
+
+ return bpfmaniov1alpha1.BpfProgCondLoaded, nil
+}
diff --git a/bpfman-operator/controllers/bpfman-agent/fentry-program_test.go b/bpfman-operator/controllers/bpfman-agent/fentry-program_test.go
new file mode 100644
index 000000000..b443b21f9
--- /dev/null
+++ b/bpfman-operator/controllers/bpfman-agent/fentry-program_test.go
@@ -0,0 +1,197 @@
+/*
+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/bpfman-operator/apis/v1alpha1"
+ bpfmanagentinternal "github.com/bpfman/bpfman/bpfman-operator/controllers/bpfman-agent/internal"
+ agenttestutils "github.com/bpfman/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils"
+ internal "github.com/bpfman/bpfman/bpfman-operator/internal"
+ testutils "github.com/bpfman/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 TestFentryProgramControllerCreate(t *testing.T) {
+ var (
+ name = "fakeFentryProgram"
+ namespace = "bpfman"
+ bytecodePath = "/tmp/hello.o"
+ bpfFunctionName = "test_fentry"
+ functionName = "do_unlinkat"
+ fakeNode = testutils.NewNode("fake-control-plane")
+ ctx = context.TODO()
+ bpfProgName = fmt.Sprintf("%s-%s-%s", name, fakeNode.Name, "do-unlinkat")
+ bpfProg = &bpfmaniov1alpha1.BpfProgram{}
+ fakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81"
+ )
+ // A FentryProgram object with metadata and spec.
+ Fentry := &bpfmaniov1alpha1.FentryProgram{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ },
+ Spec: bpfmaniov1alpha1.FentryProgramSpec{
+ BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{
+ BpfFunctionName: bpfFunctionName,
+ NodeSelector: metav1.LabelSelector{},
+ ByteCode: bpfmaniov1alpha1.BytecodeSelector{
+ Path: &bytecodePath,
+ },
+ },
+ FunctionName: functionName,
+ },
+ }
+
+ // Objects to track in the fake client.
+ objs := []runtime.Object{fakeNode, Fentry}
+
+ // Register operator types with the runtime scheme.
+ s := scheme.Scheme
+ s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, Fentry)
+ s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.FentryProgramList{})
+ s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgram{})
+ s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgramList{})
+
+ // Create a fake client to mock API calls.
+ cl := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build()
+
+ cli := agenttestutils.NewBpfmanClientFake()
+
+ rc := ReconcilerCommon{
+ Client: cl,
+ Scheme: s,
+ BpfmanClient: cli,
+ NodeName: fakeNode.Name,
+ }
+
+ // 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}
+
+ // Mock request to simulate Reconcile() being called on an event for a
+ // watched resource .
+ req := reconcile.Request{
+ NamespacedName: types.NamespacedName{
+ Name: name,
+ Namespace: namespace,
+ },
+ }
+
+ // First reconcile should create the bpf program object
+ res, err := r.Reconcile(ctx, req)
+ if err != nil {
+ t.Fatalf("reconcile: (%v)", err)
+ }
+
+ // Check the BpfProgram Object was created successfully
+ err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName, Namespace: metav1.NamespaceAll}, 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.BpfProgramOwnerLabel], name)
+ // node Label was correctly set
+ require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name)
+ // fentry function Annotation was correctly set
+ require.Equal(t, bpfProg.Annotations[internal.FentryProgramFunction], functionName)
+ // 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)
+ }
+
+ // Require no requeue
+ require.False(t, res.Requeue)
+ expectedLoadReq := &gobpfman.LoadRequest{
+ Bytecode: &gobpfman.BytecodeLocation{
+ Location: &gobpfman.BytecodeLocation_File{File: bytecodePath},
+ },
+ Name: bpfFunctionName,
+ ProgramType: *internal.Tracing.Uint32(),
+ Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProg.UID), internal.ProgramNameKey: name},
+ MapOwnerId: nil,
+ Attach: &gobpfman.AttachInfo{
+ Info: &gobpfman.AttachInfo_FentryAttachInfo{
+ FentryAttachInfo: &gobpfman.FentryAttachInfo{
+ FnName: functionName,
+ },
+ },
+ },
+ }
+
+ // Check that the bpfProgram's programs was correctly updated
+ err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName, Namespace: metav1.NamespaceAll}, bpfProg)
+ require.NoError(t, err)
+
+ // prog ID should already have been set
+ id, err := bpfmanagentinternal.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 = cl.Get(ctx, types.NamespacedName{Name: bpfProgName, Namespace: metav1.NamespaceAll}, bpfProg)
+ require.NoError(t, err)
+
+ require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type)
+}
diff --git a/bpfman-operator/controllers/bpfman-agent/fexit-program.go b/bpfman-operator/controllers/bpfman-agent/fexit-program.go
new file mode 100644
index 000000000..bb2bfc9ba
--- /dev/null
+++ b/bpfman-operator/controllers/bpfman-agent/fexit-program.go
@@ -0,0 +1,329 @@
+/*
+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/bpfman-operator/apis/v1alpha1"
+ bpfmanagentinternal "github.com/bpfman/bpfman/bpfman-operator/controllers/bpfman-agent/internal"
+ "github.com/bpfman/bpfman/bpfman-operator/internal"
+ gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1"
+
+ v1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/types"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/builder"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/handler"
+ "sigs.k8s.io/controller-runtime/pkg/log"
+ "sigs.k8s.io/controller-runtime/pkg/predicate"
+ "sigs.k8s.io/controller-runtime/pkg/source"
+)
+
+//+kubebuilder:rbac:groups=bpfman.io,resources=fexitprograms,verbs=get;list;watch
+
+// BpfProgramReconciler reconciles a BpfProgram object
+type FexitProgramReconciler struct {
+ ReconcilerCommon
+ currentFexitProgram *bpfmaniov1alpha1.FexitProgram
+ ourNode *v1.Node
+}
+
+func (r *FexitProgramReconciler) getRecCommon() *ReconcilerCommon {
+ return &r.ReconcilerCommon
+}
+
+func (r *FexitProgramReconciler) getFinalizer() string {
+ return internal.FexitProgramControllerFinalizer
+}
+
+func (r *FexitProgramReconciler) getRecType() string {
+ return internal.Tracing.String()
+}
+
+// SetupWithManager sets up the controller with the Manager.
+// The Bpfman-Agent should reconcile whenever a FexitProgram is updated,
+// load the program to the node via bpfman, and then create a bpfProgram object
+// to reflect per node state information.
+func (r *FexitProgramReconciler) SetupWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&bpfmaniov1alpha1.FexitProgram{}, builder.WithPredicates(predicate.And(predicate.GenerationChangedPredicate{}, predicate.ResourceVersionChangedPredicate{}))).
+ Owns(&bpfmaniov1alpha1.BpfProgram{},
+ builder.WithPredicates(predicate.And(
+ internal.BpfProgramTypePredicate(internal.Tracing.String()),
+ internal.BpfProgramNodePredicate(r.NodeName)),
+ ),
+ ).
+ // Only trigger reconciliation if node labels change since that could
+ // make the FexitProgram no longer select the Node. Additionally only
+ // care about node events specific to our node
+ Watches(
+ &source.Kind{Type: &v1.Node{}},
+ &handler.EnqueueRequestForObject{},
+ builder.WithPredicates(predicate.And(predicate.LabelChangedPredicate{}, nodePredicate(r.NodeName))),
+ ).
+ Complete(r)
+}
+
+func (r *FexitProgramReconciler) expectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfProgramList, error) {
+ progs := &bpfmaniov1alpha1.BpfProgramList{}
+
+ // sanitize fexit name to work in a bpfProgram name
+ sanatizedFexit := strings.Replace(strings.Replace(r.currentFexitProgram.Spec.FunctionName, "/", "-", -1), "_", "-", -1)
+ bpfProgramName := fmt.Sprintf("%s-%s-%s", r.currentFexitProgram.Name, r.NodeName, sanatizedFexit)
+
+ annotations := map[string]string{internal.FexitProgramFunction: r.currentFexitProgram.Spec.FunctionName}
+
+ prog, err := r.createBpfProgram(ctx, bpfProgramName, r.getFinalizer(), r.currentFexitProgram, r.getRecType(), annotations)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err)
+ }
+
+ progs.Items = append(progs.Items, *prog)
+
+ return progs, nil
+}
+
+func (r *FexitProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ // Initialize node and current program
+ r.currentFexitProgram = &bpfmaniov1alpha1.FexitProgram{}
+ r.ourNode = &v1.Node{}
+ r.Logger = ctrl.Log.WithName("fexit")
+
+ ctxLogger := log.FromContext(ctx)
+ ctxLogger.Info("Reconcile Fexit: Enter", "ReconcileKey", req)
+
+ // Lookup K8s node object for this bpfman-agent This should always succeed
+ if err := r.Get(ctx, types.NamespacedName{Namespace: v1.NamespaceAll, Name: r.NodeName}, r.ourNode); err != nil {
+ return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfman-agent node %s : %v",
+ req.NamespacedName, err)
+ }
+
+ fexitPrograms := &bpfmaniov1alpha1.FexitProgramList{}
+
+ opts := []client.ListOption{}
+
+ if err := r.List(ctx, fexitPrograms, opts...); err != nil {
+ return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting FexitPrograms for full reconcile %s : %v",
+ req.NamespacedName, err)
+ }
+
+ if len(fexitPrograms.Items) == 0 {
+ r.Logger.Info("FexitProgramController found no Fexit Programs")
+ return ctrl.Result{Requeue: false}, nil
+ }
+
+ // Get existing ebpf state from bpfman.
+ programMap, err := bpfmanagentinternal.ListBpfmanPrograms(ctx, r.BpfmanClient, internal.Tracing)
+ if err != nil {
+ r.Logger.Error(err, "failed to list loaded bpfman programs")
+ return ctrl.Result{Requeue: true, RequeueAfter: retryDurationAgent}, nil
+ }
+
+ // Reconcile each FexitProgram. Don't return error here because it will trigger an infinite reconcile loop, instead
+ // report the error to user and retry if specified. For some errors the controller may not decide to retry.
+ // Note: This only results in grpc calls to bpfman if we need to change something
+ requeue := false // initialize requeue to false
+ for _, fexitProgram := range fexitPrograms.Items {
+ r.Logger.Info("FexitProgramController is reconciling", "currentFexitProgram", fexitProgram.Name)
+ r.currentFexitProgram = &fexitProgram
+ result, err := reconcileProgram(ctx, r, r.currentFexitProgram, &r.currentFexitProgram.Spec.BpfProgramCommon, r.ourNode, programMap)
+ if err != nil {
+ r.Logger.Error(err, "Reconciling FexitProgram Failed", "FexitProgramName", r.currentFexitProgram.Name, "ReconcileResult", result.String())
+ }
+
+ switch result {
+ case internal.Unchanged:
+ // continue with next program
+ case internal.Updated:
+ // return
+ return ctrl.Result{Requeue: false}, nil
+ case internal.Requeue:
+ // remember to do a requeue when we're done and continue with next program
+ requeue = true
+ }
+ }
+
+ if requeue {
+ // A requeue has been requested
+ return ctrl.Result{RequeueAfter: retryDurationAgent}, nil
+ } else {
+ // We've made it through all the programs in the list without anything being
+ // updated and a reque has not been requested.
+ return ctrl.Result{Requeue: false}, nil
+ }
+}
+
+func (r *FexitProgramReconciler) buildFexitLoadRequest(
+ bytecode *gobpfman.BytecodeLocation,
+ uuid string,
+ bpfProgram *bpfmaniov1alpha1.BpfProgram,
+ mapOwnerId *uint32) *gobpfman.LoadRequest {
+
+ return &gobpfman.LoadRequest{
+ Bytecode: bytecode,
+ Name: r.currentFexitProgram.Spec.BpfFunctionName,
+ ProgramType: uint32(internal.Tracing),
+ Attach: &gobpfman.AttachInfo{
+ Info: &gobpfman.AttachInfo_FexitAttachInfo{
+ FexitAttachInfo: &gobpfman.FexitAttachInfo{
+ FnName: bpfProgram.Annotations[internal.FexitProgramFunction],
+ },
+ },
+ },
+ Metadata: map[string]string{internal.UuidMetadataKey: uuid, internal.ProgramNameKey: r.currentFexitProgram.Name},
+ GlobalData: r.currentFexitProgram.Spec.GlobalData,
+ MapOwnerId: mapOwnerId,
+ }
+}
+
+// reconcileBpfmanPrograms ONLY reconciles the bpfman state for a single BpfProgram.
+// It does not interact with the k8s API in any way.
+func (r *FexitProgramReconciler) reconcileBpfmanProgram(ctx context.Context,
+ existingBpfPrograms map[string]*gobpfman.ListResponse_ListResult,
+ bytecodeSelector *bpfmaniov1alpha1.BytecodeSelector,
+ bpfProgram *bpfmaniov1alpha1.BpfProgram,
+ isNodeSelected bool,
+ isBeingDeleted bool,
+ mapOwnerStatus *MapOwnerParamStatus) (bpfmaniov1alpha1.BpfProgramConditionType, error) {
+
+ r.Logger.V(1).Info("Existing bpfProgram", "UUID", bpfProgram.UID, "Name", bpfProgram.Name, "CurrentFexitProgram", r.currentFexitProgram.Name)
+
+ uuid := bpfProgram.UID
+
+ getLoadRequest := func() (*gobpfman.LoadRequest, bpfmaniov1alpha1.BpfProgramConditionType, error) {
+ bytecode, err := bpfmanagentinternal.GetBytecode(r.Client, bytecodeSelector)
+ if err != nil {
+ return nil, bpfmaniov1alpha1.BpfProgCondBytecodeSelectorError, fmt.Errorf("failed to process bytecode selector: %v", err)
+ }
+ loadRequest := r.buildFexitLoadRequest(bytecode, string(uuid), bpfProgram, mapOwnerStatus.mapOwnerId)
+ return loadRequest, bpfmaniov1alpha1.BpfProgCondNone, nil
+ }
+
+ existingProgram, doesProgramExist := existingBpfPrograms[string(uuid)]
+ if !doesProgramExist {
+ r.Logger.V(1).Info("FexitProgram doesn't exist on node")
+
+ // If FexitProgram is being deleted just exit
+ if isBeingDeleted {
+ return bpfmaniov1alpha1.BpfProgCondUnloaded, nil
+ }
+
+ // Make sure if we're not selected just exit
+ if !isNodeSelected {
+ return bpfmaniov1alpha1.BpfProgCondNotSelected, nil
+ }
+
+ // Make sure if the Map Owner is set but not found then just exit
+ if mapOwnerStatus.isSet && !mapOwnerStatus.isFound {
+ return bpfmaniov1alpha1.BpfProgCondMapOwnerNotFound, nil
+ }
+
+ // Make sure if the Map Owner is set but not loaded then just exit
+ if mapOwnerStatus.isSet && !mapOwnerStatus.isLoaded {
+ return bpfmaniov1alpha1.BpfProgCondMapOwnerNotLoaded, nil
+ }
+
+ // otherwise load it
+ loadRequest, condition, err := getLoadRequest()
+ if err != nil {
+ return condition, err
+ }
+
+ r.progId, err = bpfmanagentinternal.LoadBpfmanProgram(ctx, r.BpfmanClient, loadRequest)
+ if err != nil {
+ r.Logger.Error(err, "Failed to load FexitProgram")
+ return bpfmaniov1alpha1.BpfProgCondNotLoaded, nil
+ }
+
+ r.Logger.Info("bpfman called to load FexitProgram on Node", "Name", bpfProgram.Name, "UUID", uuid)
+ return bpfmaniov1alpha1.BpfProgCondLoaded, nil
+ }
+
+ // prog ID should already have been set if program exists
+ id, err := bpfmanagentinternal.GetID(bpfProgram)
+ if err != nil {
+ r.Logger.Error(err, "Failed to get program ID")
+ return bpfmaniov1alpha1.BpfProgCondNotLoaded, nil
+ }
+
+ // BpfProgram exists but either FexitProgram is being deleted, node is no
+ // longer selected, or map is not available....unload program
+ if isBeingDeleted || !isNodeSelected ||
+ (mapOwnerStatus.isSet && (!mapOwnerStatus.isFound || !mapOwnerStatus.isLoaded)) {
+ r.Logger.V(1).Info("FexitProgram exists on Node but is scheduled for deletion, not selected, or map not available",
+ "isDeleted", isBeingDeleted, "isSelected", isNodeSelected, "mapIsSet", mapOwnerStatus.isSet,
+ "mapIsFound", mapOwnerStatus.isFound, "mapIsLoaded", mapOwnerStatus.isLoaded, "id", id)
+
+ if err := bpfmanagentinternal.UnloadBpfmanProgram(ctx, r.BpfmanClient, *id); err != nil {
+ r.Logger.Error(err, "Failed to unload FexitProgram")
+ return bpfmaniov1alpha1.BpfProgCondNotUnloaded, nil
+ }
+
+ r.Logger.Info("bpfman called to unload FexitProgram on Node", "Name", bpfProgram.Name, "UUID", uuid)
+
+ if isBeingDeleted {
+ return bpfmaniov1alpha1.BpfProgCondUnloaded, nil
+ }
+
+ if !isNodeSelected {
+ return bpfmaniov1alpha1.BpfProgCondNotSelected, nil
+ }
+
+ if mapOwnerStatus.isSet && !mapOwnerStatus.isFound {
+ return bpfmaniov1alpha1.BpfProgCondMapOwnerNotFound, nil
+ }
+
+ if mapOwnerStatus.isSet && !mapOwnerStatus.isLoaded {
+ return bpfmaniov1alpha1.BpfProgCondMapOwnerNotLoaded, nil
+ }
+ }
+
+ // BpfProgram exists but is not correct state, unload and recreate
+ loadRequest, condition, err := getLoadRequest()
+ if err != nil {
+ return condition, err
+ }
+
+ r.Logger.V(1).WithValues("expectedProgram", loadRequest).WithValues("existingProgram", existingProgram).Info("StateMatch")
+
+ isSame, reasons := bpfmanagentinternal.DoesProgExist(existingProgram, loadRequest)
+ if !isSame {
+ r.Logger.V(1).Info("FexitProgram is in wrong state, unloading and reloading", "Reason", reasons)
+ if err := bpfmanagentinternal.UnloadBpfmanProgram(ctx, r.BpfmanClient, *id); err != nil {
+ r.Logger.Error(err, "Failed to unload FexitProgram")
+ return bpfmaniov1alpha1.BpfProgCondNotUnloaded, nil
+ }
+
+ r.progId, err = bpfmanagentinternal.LoadBpfmanProgram(ctx, r.BpfmanClient, loadRequest)
+ if err != nil {
+ r.Logger.Error(err, "Failed to load FexitProgram")
+ return bpfmaniov1alpha1.BpfProgCondNotLoaded, err
+ }
+
+ r.Logger.Info("bpfman called to reload FexitProgram on Node", "Name", bpfProgram.Name, "UUID", uuid)
+ } else {
+ // Program exists and bpfProgram K8s Object is up to date
+ r.Logger.V(1).Info("Ignoring Object Change nothing to do in bpfman")
+ r.progId = id
+ }
+
+ return bpfmaniov1alpha1.BpfProgCondLoaded, nil
+}
diff --git a/bpfman-operator/controllers/bpfman-agent/fexit-program_test.go b/bpfman-operator/controllers/bpfman-agent/fexit-program_test.go
new file mode 100644
index 000000000..02850c930
--- /dev/null
+++ b/bpfman-operator/controllers/bpfman-agent/fexit-program_test.go
@@ -0,0 +1,197 @@
+/*
+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/bpfman-operator/apis/v1alpha1"
+ bpfmanagentinternal "github.com/bpfman/bpfman/bpfman-operator/controllers/bpfman-agent/internal"
+ agenttestutils "github.com/bpfman/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils"
+ internal "github.com/bpfman/bpfman/bpfman-operator/internal"
+ testutils "github.com/bpfman/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 TestFexitProgramControllerCreate(t *testing.T) {
+ var (
+ name = "fakeFexitProgram"
+ namespace = "bpfman"
+ bytecodePath = "/tmp/fexit.o"
+ bpfFunctionName = "test_fexit"
+ functionName = "do_unlinkat"
+ fakeNode = testutils.NewNode("fake-control-plane")
+ ctx = context.TODO()
+ bpfProgName = fmt.Sprintf("%s-%s-%s", name, fakeNode.Name, "do-unlinkat")
+ bpfProg = &bpfmaniov1alpha1.BpfProgram{}
+ fakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81"
+ )
+ // A FexitProgram object with metadata and spec.
+ Fexit := &bpfmaniov1alpha1.FexitProgram{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ },
+ Spec: bpfmaniov1alpha1.FexitProgramSpec{
+ BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{
+ BpfFunctionName: bpfFunctionName,
+ NodeSelector: metav1.LabelSelector{},
+ ByteCode: bpfmaniov1alpha1.BytecodeSelector{
+ Path: &bytecodePath,
+ },
+ },
+ FunctionName: functionName,
+ },
+ }
+
+ // Objects to track in the fake client.
+ objs := []runtime.Object{fakeNode, Fexit}
+
+ // Register operator types with the runtime scheme.
+ s := scheme.Scheme
+ s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, Fexit)
+ s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.FexitProgramList{})
+ s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgram{})
+ s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgramList{})
+
+ // Create a fake client to mock API calls.
+ cl := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build()
+
+ cli := agenttestutils.NewBpfmanClientFake()
+
+ rc := ReconcilerCommon{
+ Client: cl,
+ Scheme: s,
+ BpfmanClient: cli,
+ NodeName: fakeNode.Name,
+ }
+
+ // 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}
+
+ // Mock request to simulate Reconcile() being called on an event for a
+ // watched resource .
+ req := reconcile.Request{
+ NamespacedName: types.NamespacedName{
+ Name: name,
+ Namespace: namespace,
+ },
+ }
+
+ // First reconcile should create the bpf program object
+ res, err := r.Reconcile(ctx, req)
+ if err != nil {
+ t.Fatalf("reconcile: (%v)", err)
+ }
+
+ // Check the BpfProgram Object was created successfully
+ err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName, Namespace: metav1.NamespaceAll}, 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.BpfProgramOwnerLabel], name)
+ // node Label was correctly set
+ require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name)
+ // fexit function Annotation was correctly set
+ require.Equal(t, bpfProg.Annotations[internal.FexitProgramFunction], functionName)
+ // 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)
+ }
+
+ // Require no requeue
+ require.False(t, res.Requeue)
+ expectedLoadReq := &gobpfman.LoadRequest{
+ Bytecode: &gobpfman.BytecodeLocation{
+ Location: &gobpfman.BytecodeLocation_File{File: bytecodePath},
+ },
+ Name: bpfFunctionName,
+ ProgramType: *internal.Tracing.Uint32(),
+ Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProg.UID), internal.ProgramNameKey: name},
+ MapOwnerId: nil,
+ Attach: &gobpfman.AttachInfo{
+ Info: &gobpfman.AttachInfo_FexitAttachInfo{
+ FexitAttachInfo: &gobpfman.FexitAttachInfo{
+ FnName: functionName,
+ },
+ },
+ },
+ }
+
+ // Check that the bpfProgram's programs was correctly updated
+ err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName, Namespace: metav1.NamespaceAll}, bpfProg)
+ require.NoError(t, err)
+
+ // prog ID should already have been set
+ id, err := bpfmanagentinternal.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 = cl.Get(ctx, types.NamespacedName{Name: bpfProgName, Namespace: metav1.NamespaceAll}, bpfProg)
+ require.NoError(t, err)
+
+ require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type)
+}
diff --git a/bpfman-operator/controllers/bpfman-agent/internal/bpfman-core.go b/bpfman-operator/controllers/bpfman-agent/internal/bpfman-core.go
index 01564bb09..af9096970 100644
--- a/bpfman-operator/controllers/bpfman-agent/internal/bpfman-core.go
+++ b/bpfman-operator/controllers/bpfman-agent/internal/bpfman-core.go
@@ -165,8 +165,10 @@ func GetBpfmanProgram(ctx context.Context, bpfmanClient gobpfman.BpfmanClient, u
return nil, err
}
- if len(listResponse.Results) != 1 {
- return nil, fmt.Errorf("multiple programs found for uuid: %+v ", uuid)
+ if len(listResponse.Results) == 0 {
+ return nil, fmt.Errorf("unable to find program for uuid: %+v ", uuid)
+ } else if len(listResponse.Results) != 1 {
+ return nil, fmt.Errorf("multiple programs found for uuid: %+v instances: %d", uuid, len(listResponse.Results))
}
return listResponse.Results[0], nil
diff --git a/bpfman-operator/controllers/bpfman-agent/internal/cmp.go b/bpfman-operator/controllers/bpfman-agent/internal/cmp.go
index 7c6f1d66f..1d316c1de 100644
--- a/bpfman-operator/controllers/bpfman-agent/internal/cmp.go
+++ b/bpfman-operator/controllers/bpfman-agent/internal/cmp.go
@@ -136,7 +136,7 @@ func DoesProgExist(actual *gobpfman.ListResponse_ListResult, expected *gobpfman.
if actualKprobe.FnName != expectedKprobe.FnName ||
actualKprobe.Offset != expectedKprobe.Offset ||
actualKprobe.Retprobe != expectedKprobe.Retprobe ||
- !reflect.DeepEqual(actualKprobe.Namespace, expectedKprobe.Namespace) {
+ !reflect.DeepEqual(actualKprobe.ContainerPid, expectedKprobe.ContainerPid) {
reasons = append(reasons, fmt.Sprintf("Expected Kprobe to be %v but found %v",
expectedKprobe, actualKprobe))
}
@@ -150,11 +150,29 @@ func DoesProgExist(actual *gobpfman.ListResponse_ListResult, expected *gobpfman.
actualUprobe.Target != expectedUprobe.Target ||
actualUprobe.Retprobe != expectedUprobe.Retprobe ||
actualUprobe.Pid != expectedUprobe.Pid ||
- !reflect.DeepEqual(actualUprobe.Namespace, expectedUprobe.Namespace) {
+ !reflect.DeepEqual(actualUprobe.ContainerPid, expectedUprobe.ContainerPid) {
reasons = append(reasons, fmt.Sprintf("Expected Uprobe to be %v but found %v",
expectedUprobe, actualUprobe))
}
}
+
+ actualFentry := actualAttach.GetFentryAttachInfo()
+ expectedFentry := expectedAttach.GetFentryAttachInfo()
+ if actualFentry != nil && expectedFentry != nil {
+ if !reflect.DeepEqual(actualFentry.FnName, expectedFentry.FnName) {
+ reasons = append(reasons, fmt.Sprintf("Expected Fentry to be %v but found %v",
+ expectedFentry, actualFentry))
+ }
+ }
+
+ actualFexit := actualAttach.GetFexitAttachInfo()
+ expectedFexit := expectedAttach.GetFexitAttachInfo()
+ if actualFexit != nil && expectedFexit != nil {
+ if !reflect.DeepEqual(actualFexit.FnName, expectedFexit.FnName) {
+ reasons = append(reasons, fmt.Sprintf("Expected Fexit to be %v but found %v",
+ expectedFexit, actualFexit))
+ }
+ }
}
if len(reasons) == 0 {
diff --git a/bpfman-operator/controllers/bpfman-agent/kprobe-program.go b/bpfman-operator/controllers/bpfman-agent/kprobe-program.go
index ed6ea8088..a519a633e 100644
--- a/bpfman-operator/controllers/bpfman-agent/kprobe-program.go
+++ b/bpfman-operator/controllers/bpfman-agent/kprobe-program.go
@@ -85,21 +85,19 @@ func (r *KprobeProgramReconciler) SetupWithManager(mgr ctrl.Manager) error {
func (r *KprobeProgramReconciler) expectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfProgramList, error) {
progs := &bpfmaniov1alpha1.BpfProgramList{}
- for _, function := range r.currentKprobeProgram.Spec.FunctionNames {
- // sanitize kprobe name to work in a bpfProgram name
- sanatizedKprobe := strings.Replace(strings.Replace(function, "/", "-", -1), "_", "-", -1)
- bpfProgramName := fmt.Sprintf("%s-%s-%s", r.currentKprobeProgram.Name, r.NodeName, sanatizedKprobe)
+ // sanitize kprobe name to work in a bpfProgram name
+ sanatizedKprobe := strings.Replace(strings.Replace(r.currentKprobeProgram.Spec.FunctionName, "/", "-", -1), "_", "-", -1)
+ bpfProgramName := fmt.Sprintf("%s-%s-%s", r.currentKprobeProgram.Name, r.NodeName, sanatizedKprobe)
- annotations := map[string]string{internal.KprobeProgramFunction: function}
+ annotations := map[string]string{internal.KprobeProgramFunction: r.currentKprobeProgram.Spec.FunctionName}
- prog, err := r.createBpfProgram(ctx, bpfProgramName, r.getFinalizer(), r.currentKprobeProgram, r.getRecType(), annotations)
- if err != nil {
- return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err)
- }
-
- progs.Items = append(progs.Items, *prog)
+ prog, err := r.createBpfProgram(ctx, bpfProgramName, r.getFinalizer(), r.currentKprobeProgram, r.getRecType(), annotations)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err)
}
+ progs.Items = append(progs.Items, *prog)
+
return progs, nil
}
@@ -179,8 +177,8 @@ func (r *KprobeProgramReconciler) buildKprobeLoadRequest(
bpfProgram *bpfmaniov1alpha1.BpfProgram,
mapOwnerId *uint32) *gobpfman.LoadRequest {
- // Namespace isn't supported yet in bpfman, so set it to an empty string.
- namespace := ""
+ // Container PID isn't supported yet in bpfman, so set it to zero.
+ var container_pid int32 = 0
return &gobpfman.LoadRequest{
Bytecode: bytecode,
@@ -189,10 +187,10 @@ func (r *KprobeProgramReconciler) buildKprobeLoadRequest(
Attach: &gobpfman.AttachInfo{
Info: &gobpfman.AttachInfo_KprobeAttachInfo{
KprobeAttachInfo: &gobpfman.KprobeAttachInfo{
- FnName: bpfProgram.Annotations[internal.KprobeProgramFunction],
- Offset: r.currentKprobeProgram.Spec.Offset,
- Retprobe: r.currentKprobeProgram.Spec.RetProbe,
- Namespace: &namespace,
+ FnName: bpfProgram.Annotations[internal.KprobeProgramFunction],
+ Offset: r.currentKprobeProgram.Spec.Offset,
+ Retprobe: r.currentKprobeProgram.Spec.RetProbe,
+ ContainerPid: &container_pid,
},
},
},
diff --git a/bpfman-operator/controllers/bpfman-agent/kprobe-program_test.go b/bpfman-operator/controllers/bpfman-agent/kprobe-program_test.go
index ac32a2d71..5370a3d78 100644
--- a/bpfman-operator/controllers/bpfman-agent/kprobe-program_test.go
+++ b/bpfman-operator/controllers/bpfman-agent/kprobe-program_test.go
@@ -45,19 +45,19 @@ import (
func TestKprobeProgramControllerCreate(t *testing.T) {
var (
- name = "fakeKprobeProgram"
- namespace = "bpfman"
- bytecodePath = "/tmp/hello.o"
- bpfFunctionName = "test"
- functionName = "try_to_wake_up"
- offset = 0
- retprobe = false
- kprobenamespace = ""
- fakeNode = testutils.NewNode("fake-control-plane")
- ctx = context.TODO()
- bpfProgName = fmt.Sprintf("%s-%s-%s", name, fakeNode.Name, "try-to-wake-up")
- bpfProg = &bpfmaniov1alpha1.BpfProgram{}
- fakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81"
+ name = "fakeKprobeProgram"
+ namespace = "bpfman"
+ bytecodePath = "/tmp/hello.o"
+ bpfFunctionName = "test"
+ functionName = "try_to_wake_up"
+ offset = 0
+ retprobe = false
+ kprobecontainerpid int32 = 0
+ fakeNode = testutils.NewNode("fake-control-plane")
+ ctx = context.TODO()
+ bpfProgName = fmt.Sprintf("%s-%s-%s", name, fakeNode.Name, "try-to-wake-up")
+ bpfProg = &bpfmaniov1alpha1.BpfProgram{}
+ fakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81"
)
// A KprobeProgram object with metadata and spec.
Kprobe := &bpfmaniov1alpha1.KprobeProgram{
@@ -72,9 +72,9 @@ func TestKprobeProgramControllerCreate(t *testing.T) {
Path: &bytecodePath,
},
},
- FunctionNames: []string{functionName},
- Offset: uint64(offset),
- RetProbe: retprobe,
+ FunctionName: functionName,
+ Offset: uint64(offset),
+ RetProbe: retprobe,
},
}
@@ -164,10 +164,10 @@ func TestKprobeProgramControllerCreate(t *testing.T) {
Attach: &gobpfman.AttachInfo{
Info: &gobpfman.AttachInfo_KprobeAttachInfo{
KprobeAttachInfo: &gobpfman.KprobeAttachInfo{
- FnName: functionName,
- Offset: uint64(offset),
- Retprobe: retprobe,
- Namespace: &kprobenamespace,
+ FnName: functionName,
+ Offset: uint64(offset),
+ Retprobe: retprobe,
+ ContainerPid: &kprobecontainerpid,
},
},
},
diff --git a/bpfman-operator/controllers/bpfman-agent/uprobe-program.go b/bpfman-operator/controllers/bpfman-agent/uprobe-program.go
index 8231a1815..017d3189e 100644
--- a/bpfman-operator/controllers/bpfman-agent/uprobe-program.go
+++ b/bpfman-operator/controllers/bpfman-agent/uprobe-program.go
@@ -19,6 +19,7 @@ package bpfmanagent
import (
"context"
"fmt"
+ "strconv"
"strings"
"k8s.io/apimachinery/pkg/types"
@@ -73,30 +74,105 @@ func (r *UprobeProgramReconciler) SetupWithManager(mgr ctrl.Manager) error {
internal.BpfProgramNodePredicate(r.NodeName)),
),
).
- // Only trigger reconciliation if node labels change since that could
- // make the UprobeProgram no longer select the Node. Additionally only
- // care about node events specific to our node
+ // Trigger reconciliation if node labels change since that could make
+ // the UprobeProgram 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(
&source.Kind{Type: &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(
+ &source.Kind{Type: &v1.Pod{}},
+ &handler.EnqueueRequestForObject{},
+ builder.WithPredicates(podOnNodePredicate(r.NodeName)),
+ ).
Complete(r)
}
+// Figure out the list of container pids the uProbe needs to be attached to.
+func (r *UprobeProgramReconciler) getUprobeContainerInfo(ctx context.Context) (*[]containerInfo, error) {
+
+ clientSet, err := getClientset()
+ if err != nil {
+ return nil, fmt.Errorf("failed to get clientset: %v", err)
+ }
+
+ // Get the list of pods that match the selector.
+ podList, err := getPodsForNode(ctx, clientSet, r.currentUprobeProgram.Spec.Containers, r.NodeName)
+ 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.
+ containers, err := getContainerInfo(podList, r.currentUprobeProgram.Spec.Containers.ContainerNames, r.Logger)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get container info: %v", err)
+ }
+
+ r.Logger.V(1).Info("from getContainerInfo", "containers", containers)
+
+ return containers, nil
+}
+
func (r *UprobeProgramReconciler) expectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfProgramList, error) {
progs := &bpfmaniov1alpha1.BpfProgramList{}
- for _, target := range r.currentUprobeProgram.Spec.Targets {
- // sanitize uprobe name to work in a bpfProgram name
- sanatizedUprobe := strings.Replace(strings.Replace(target, "/", "-", -1), "_", "-", -1)
- bpfProgramName := fmt.Sprintf("%s-%s-%s", r.currentUprobeProgram.Name, r.NodeName, sanatizedUprobe)
+ // sanitize uprobe name to work in a bpfProgram name
+ sanatizedUprobe := strings.Replace(strings.Replace(r.currentUprobeProgram.Spec.Target, "/", "-", -1), "_", "-", -1)
+ bpfProgramNameBase := fmt.Sprintf("%s-%s-%s", r.currentUprobeProgram.Name, r.NodeName, sanatizedUprobe)
- annotations := map[string]string{internal.UprobeProgramTarget: target}
+ if r.currentUprobeProgram.Spec.Containers != nil {
- prog, err := r.createBpfProgram(ctx, bpfProgramName, r.getFinalizer(), r.currentUprobeProgram, r.getRecType(), annotations)
+ // Some containers were specified, so we need to get the containers
+ containerInfo, err := r.getUprobeContainerInfo(ctx)
if err != nil {
- return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err)
+ 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.UprobeProgramTarget: r.currentUprobeProgram.Spec.Target,
+ internal.UprobeNoContainersOnNode: "true",
+ }
+
+ bpfProgramName := fmt.Sprintf("%s-%s", bpfProgramNameBase, "no-containers-on-node")
+
+ prog, err := r.createBpfProgram(ctx, bpfProgramName, r.getFinalizer(), r.currentUprobeProgram, r.getRecType(), annotations)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramNameBase, err)
+ }
+
+ progs.Items = append(progs.Items, *prog)
+ } else {
+
+ // We got some containers, so create the bpfPrograms for each one.
+ for i := range *containerInfo {
+ container := (*containerInfo)[i]
+
+ annotations := map[string]string{internal.UprobeProgramTarget: r.currentUprobeProgram.Spec.Target}
+ annotations[internal.UprobeContainerPid] = strconv.FormatInt(container.pid, 10)
+
+ bpfProgramName := fmt.Sprintf("%s-%s-%s", bpfProgramNameBase, container.podName, container.containerName)
+
+ prog, err := r.createBpfProgram(ctx, bpfProgramName, r.getFinalizer(), r.currentUprobeProgram, r.getRecType(), annotations)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err)
+ }
+
+ progs.Items = append(progs.Items, *prog)
+ }
+ }
+ } else {
+ annotations := map[string]string{internal.UprobeProgramTarget: r.currentUprobeProgram.Spec.Target}
+
+ prog, err := r.createBpfProgram(ctx, bpfProgramNameBase, r.getFinalizer(), r.currentUprobeProgram, r.getRecType(), annotations)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramNameBase, err)
}
progs.Items = append(progs.Items, *prog)
@@ -182,8 +258,33 @@ func (r *UprobeProgramReconciler) buildUprobeLoadRequest(
bpfProgram *bpfmaniov1alpha1.BpfProgram,
mapOwnerId *uint32) *gobpfman.LoadRequest {
- // Namespace isn't supported yet in bpfman, so set it to an empty string.
- namespace := ""
+ var uprobeAttachInfo *gobpfman.UprobeAttachInfo
+
+ var containerPid int32
+ hasContainerPid := false
+
+ containerPidStr, ok := bpfProgram.Annotations[internal.UprobeContainerPid]
+
+ 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.currentUprobeProgram.Spec.FunctionName,
+ Offset: r.currentUprobeProgram.Spec.Offset,
+ Target: bpfProgram.Annotations[internal.UprobeProgramTarget],
+ Retprobe: r.currentUprobeProgram.Spec.RetProbe,
+ }
+
+ if hasContainerPid {
+ uprobeAttachInfo.ContainerPid = &containerPid
+ }
return &gobpfman.LoadRequest{
Bytecode: bytecode,
@@ -191,13 +292,7 @@ func (r *UprobeProgramReconciler) buildUprobeLoadRequest(
ProgramType: uint32(internal.Kprobe),
Attach: &gobpfman.AttachInfo{
Info: &gobpfman.AttachInfo_UprobeAttachInfo{
- UprobeAttachInfo: &gobpfman.UprobeAttachInfo{
- FnName: &r.currentUprobeProgram.Spec.FunctionName,
- Offset: r.currentUprobeProgram.Spec.Offset,
- Target: bpfProgram.Annotations[internal.UprobeProgramTarget],
- Retprobe: r.currentUprobeProgram.Spec.RetProbe,
- Namespace: &namespace,
- },
+ UprobeAttachInfo: uprobeAttachInfo,
},
},
Metadata: map[string]string{internal.UuidMetadataKey: uuid, internal.ProgramNameKey: r.currentUprobeProgram.Name},
@@ -229,6 +324,8 @@ func (r *UprobeProgramReconciler) reconcileBpfmanProgram(ctx context.Context,
return loadRequest, bpfmaniov1alpha1.BpfProgCondNone, nil
}
+ noContainers := noContainersOnNode(bpfProgram)
+
existingProgram, doesProgramExist := existingBpfPrograms[string(uuid)]
if !doesProgramExist {
r.Logger.V(1).Info("UprobeProgram doesn't exist on node")
@@ -243,6 +340,13 @@ func (r *UprobeProgramReconciler) reconcileBpfmanProgram(ctx context.Context,
return bpfmaniov1alpha1.BpfProgCondNotSelected, nil
}
+ // If a container selector is present but there were no matching
+ // containers on this node, just exit.
+ if noContainers {
+ r.Logger.V(1).Info("Program does not exist and there are no matching containers on this node")
+ return bpfmaniov1alpha1.BpfProgCondNoContainersOnNode, nil
+ }
+
// Make sure if the Map Owner is set but not found then just exit
if mapOwnerStatus.isSet && !mapOwnerStatus.isFound {
return bpfmaniov1alpha1.BpfProgCondMapOwnerNotFound, nil
@@ -278,7 +382,7 @@ func (r *UprobeProgramReconciler) reconcileBpfmanProgram(ctx context.Context,
// BpfProgram exists but either UprobeProgram is being deleted, node is no
// longer selected, or map is not available....unload program
- if isBeingDeleted || !isNodeSelected ||
+ if isBeingDeleted || !isNodeSelected || noContainers ||
(mapOwnerStatus.isSet && (!mapOwnerStatus.isFound || !mapOwnerStatus.isLoaded)) {
r.Logger.V(1).Info("UprobeProgram exists on Node but is scheduled for deletion, not selected, or map not available",
"isDeleted", isBeingDeleted, "isSelected", isNodeSelected, "mapIsSet", mapOwnerStatus.isSet,
@@ -289,7 +393,7 @@ func (r *UprobeProgramReconciler) reconcileBpfmanProgram(ctx context.Context,
return bpfmaniov1alpha1.BpfProgCondNotUnloaded, nil
}
- r.Logger.Info("bpfman called to unload UprobeProgram on Node", "Name", bpfProgram.Name, "UUID", uuid)
+ r.Logger.Info("bpfman called to unload UprobeProgram on Node", "Name", bpfProgram.Name, "Program ID", id)
if isBeingDeleted {
return bpfmaniov1alpha1.BpfProgCondUnloaded, nil
@@ -334,6 +438,7 @@ func (r *UprobeProgramReconciler) reconcileBpfmanProgram(ctx context.Context,
} else {
// Program exists and bpfProgram K8s Object is up to date
r.Logger.V(1).Info("Ignoring Object Change nothing to do in bpfman")
+ r.progId = id
}
return bpfmaniov1alpha1.BpfProgCondLoaded, nil
diff --git a/bpfman-operator/controllers/bpfman-agent/uprobe-program_test.go b/bpfman-operator/controllers/bpfman-agent/uprobe-program_test.go
index 8b45b1988..465200c57 100644
--- a/bpfman-operator/controllers/bpfman-agent/uprobe-program_test.go
+++ b/bpfman-operator/controllers/bpfman-agent/uprobe-program_test.go
@@ -32,9 +32,11 @@ import (
gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1"
"github.com/stretchr/testify/require"
+ v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
+ clientGoFake "k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -53,7 +55,6 @@ func TestUprobeProgramControllerCreate(t *testing.T) {
target = "libc"
offset = 0
retprobe = false
- uprobenamespace = ""
fakeNode = testutils.NewNode("fake-control-plane")
ctx = context.TODO()
bpfProgName = fmt.Sprintf("%s-%s-%s", name, fakeNode.Name, "libc")
@@ -74,7 +75,7 @@ func TestUprobeProgramControllerCreate(t *testing.T) {
},
},
FunctionName: functionName,
- Targets: []string{target},
+ Target: target,
Offset: uint64(offset),
RetProbe: retprobe,
},
@@ -166,11 +167,10 @@ func TestUprobeProgramControllerCreate(t *testing.T) {
Attach: &gobpfman.AttachInfo{
Info: &gobpfman.AttachInfo_UprobeAttachInfo{
UprobeAttachInfo: &gobpfman.UprobeAttachInfo{
- FnName: &functionName,
- Target: target,
- Offset: uint64(offset),
- Retprobe: retprobe,
- Namespace: &uprobenamespace,
+ FnName: &functionName,
+ Target: target,
+ Offset: uint64(offset),
+ Retprobe: retprobe,
},
},
},
@@ -206,3 +206,59 @@ func TestUprobeProgramControllerCreate(t *testing.T) {
require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type)
}
+
+func TestGetPods(t *testing.T) {
+ ctx := context.TODO()
+
+ // Create a fake clientset
+ clientset := clientGoFake.NewSimpleClientset()
+
+ // Create a ContainerSelector
+ containerSelector := &bpfmaniov1alpha1.ContainerSelector{
+ Pods: metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "app": "test",
+ },
+ },
+ Namespace: "default",
+ }
+
+ nodeName := "test-node"
+
+ // Create a Pod that matches the label selector and is on the correct node
+ pod := &v1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-pod",
+ Namespace: "default",
+ Labels: map[string]string{
+ "app": "test",
+ },
+ },
+ Spec: v1.PodSpec{
+ NodeName: nodeName,
+ },
+ }
+
+ // Add the Pod to the fake clientset
+ _, err := clientset.CoreV1().Pods("default").Create(ctx, pod, metav1.CreateOptions{})
+ require.NoError(t, err)
+
+ // Call getPods and check the returned PodList
+ podList, err := getPodsForNode(ctx, clientset, containerSelector, nodeName)
+ require.NoError(t, err)
+ require.Len(t, podList.Items, 1)
+ require.Equal(t, "test-pod", podList.Items[0].Name)
+
+ // Try another selector
+ // Create a ContainerSelector
+ containerSelector = &bpfmaniov1alpha1.ContainerSelector{
+ Pods: metav1.LabelSelector{
+ MatchLabels: map[string]string{},
+ },
+ }
+
+ podList, err = getPodsForNode(ctx, clientset, containerSelector, nodeName)
+ require.NoError(t, err)
+ require.Len(t, podList.Items, 1)
+ require.Equal(t, "test-pod", podList.Items[0].Name)
+}
diff --git a/bpfman-operator/controllers/bpfman-operator/common.go b/bpfman-operator/controllers/bpfman-operator/common.go
index d3a30ad4f..77fc675e5 100644
--- a/bpfman-operator/controllers/bpfman-operator/common.go
+++ b/bpfman-operator/controllers/bpfman-operator/common.go
@@ -91,12 +91,22 @@ func reconcileBpfProgram(ctx context.Context, rec ProgramReconciler, prog client
return ctrl.Result{Requeue: true, RequeueAfter: retryDurationOperator}, nil
}
- // Return NotYetLoaded Status if
- // BpfPrograms for each node haven't been created by bpfman-agent and the config isn't
- // being deleted.
- if len(nodes.Items) != len(bpfPrograms.Items) && prog.GetDeletionTimestamp().IsZero() {
- // Causes Requeue
- return rec.updateStatus(ctx, progName, bpfmaniov1alpha1.ProgramNotYetLoaded, "")
+ // If the program isn't being deleted, make sure that each node has at
+ // least one bpfprogram object. If not, Return NotYetLoaded Status.
+ if prog.GetDeletionTimestamp().IsZero() {
+ for _, node := range nodes.Items {
+ nodeFound := false
+ for _, program := range bpfPrograms.Items {
+ bpfProgramNode := program.ObjectMeta.Labels[internal.K8sHostLabel]
+ if node.Name == bpfProgramNode {
+ nodeFound = true
+ break
+ }
+ }
+ if !nodeFound {
+ return rec.updateStatus(ctx, progName, bpfmaniov1alpha1.ProgramNotYetLoaded, "")
+ }
+ }
}
failedBpfPrograms := []string{}
diff --git a/bpfman-operator/controllers/bpfman-operator/configmap.go b/bpfman-operator/controllers/bpfman-operator/configmap.go
index 96f7bd61f..5584c57d8 100644
--- a/bpfman-operator/controllers/bpfman-operator/configmap.go
+++ b/bpfman-operator/controllers/bpfman-operator/configmap.go
@@ -21,6 +21,7 @@ import (
"io"
"os"
"reflect"
+ "strings"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
@@ -248,6 +249,8 @@ func LoadAndConfigureBpfmanDs(config *corev1.ConfigMap, path string) *appsv1.Dae
bpfmanAgentImage := config.Data["bpfman.agent.image"]
bpfmanLogLevel := config.Data["bpfman.log.level"]
bpfmanAgentLogLevel := config.Data["bpfman.agent.log.level"]
+ bpfmanHealthProbeAddr := config.Data["bpfman.agent.healthprobe.addr"]
+ bpfmanMetricAddr := config.Data["bpfman.agent.metric.addr"]
// Annotate the log level on the ds so we get automatic restarts on changes.
if staticBpfmanDeployment.Spec.Template.ObjectMeta.Annotations == nil {
@@ -256,10 +259,32 @@ func LoadAndConfigureBpfmanDs(config *corev1.ConfigMap, path string) *appsv1.Dae
staticBpfmanDeployment.Spec.Template.ObjectMeta.Annotations["bpfman.io.bpfman.loglevel"] = bpfmanLogLevel
staticBpfmanDeployment.Spec.Template.ObjectMeta.Annotations["bpfman.io.bpfman.agent.loglevel"] = bpfmanAgentLogLevel
- staticBpfmanDeployment.Name = "bpfman-daemon"
+ staticBpfmanDeployment.Spec.Template.ObjectMeta.Annotations["bpfman.io.bpfman.agent.healthprobeaddr"] = bpfmanHealthProbeAddr
+ staticBpfmanDeployment.Spec.Template.ObjectMeta.Annotations["bpfman.io.bpfman.agent.metricaddr"] = bpfmanMetricAddr
+ staticBpfmanDeployment.Name = internal.BpfmanDsName
staticBpfmanDeployment.Namespace = config.Namespace
- staticBpfmanDeployment.Spec.Template.Spec.Containers[0].Image = bpfmanImage
- staticBpfmanDeployment.Spec.Template.Spec.Containers[1].Image = bpfmanAgentImage
+ for cindex, container := range staticBpfmanDeployment.Spec.Template.Spec.Containers {
+ if container.Name == internal.BpfmanContainerName {
+ staticBpfmanDeployment.Spec.Template.Spec.Containers[cindex].Image = bpfmanImage
+ } else if container.Name == internal.BpfmanAgentContainerName {
+ staticBpfmanDeployment.Spec.Template.Spec.Containers[cindex].Image = bpfmanAgentImage
+
+ for aindex, arg := range container.Args {
+ if bpfmanHealthProbeAddr != "" {
+ if strings.Contains(arg, "health-probe-bind-address") {
+ staticBpfmanDeployment.Spec.Template.Spec.Containers[cindex].Args[aindex] =
+ "--health-probe-bind-address=" + bpfmanHealthProbeAddr
+ }
+ }
+ if bpfmanMetricAddr != "" {
+ if strings.Contains(arg, "metrics-bind-address") {
+ staticBpfmanDeployment.Spec.Template.Spec.Containers[cindex].Args[aindex] =
+ "--metrics-bind-address=" + bpfmanMetricAddr
+ }
+ }
+ }
+ }
+ }
controllerutil.AddFinalizer(staticBpfmanDeployment, internal.BpfmanOperatorFinalizer)
return staticBpfmanDeployment
diff --git a/bpfman-operator/controllers/bpfman-operator/fentry-program.go b/bpfman-operator/controllers/bpfman-operator/fentry-program.go
new file mode 100644
index 000000000..0ec57b94e
--- /dev/null
+++ b/bpfman-operator/controllers/bpfman-operator/fentry-program.go
@@ -0,0 +1,116 @@
+/*
+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"
+
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/types"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/builder"
+
+ "sigs.k8s.io/controller-runtime/pkg/handler"
+ "sigs.k8s.io/controller-runtime/pkg/log"
+ "sigs.k8s.io/controller-runtime/pkg/predicate"
+ "sigs.k8s.io/controller-runtime/pkg/source"
+
+ bpfmaniov1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1"
+ "github.com/bpfman/bpfman/bpfman-operator/internal"
+)
+
+//+kubebuilder:rbac:groups=bpfman.io,resources=fentryprograms,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=bpfman.io,resources=fentryprograms/status,verbs=get;update;patch
+//+kubebuilder:rbac:groups=bpfman.io,resources=fentryprograms/finalizers,verbs=update
+
+type FentryProgramReconciler struct {
+ ReconcilerCommon
+}
+
+func (r *FentryProgramReconciler) getRecCommon() *ReconcilerCommon {
+ return &r.ReconcilerCommon
+}
+
+func (r *FentryProgramReconciler) getFinalizer() string {
+ return internal.FentryProgramControllerFinalizer
+}
+
+// SetupWithManager sets up the controller with the Manager.
+func (r *FentryProgramReconciler) SetupWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&bpfmaniov1alpha1.FentryProgram{}).
+ // Watch bpfPrograms which are owned by FentryPrograms
+ Watches(
+ &source.Kind{Type: &bpfmaniov1alpha1.BpfProgram{}},
+ &handler.EnqueueRequestForObject{},
+ builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.Tracing.String()))),
+ ).
+ Complete(r)
+}
+
+func (r *FentryProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ r.Logger = log.FromContext(ctx)
+
+ fentryProgram := &bpfmaniov1alpha1.FentryProgram{}
+ if err := r.Get(ctx, req.NamespacedName, fentryProgram); err != nil {
+ // Reconcile was triggered by bpfProgram event, get parent FentryProgram Object.
+ if errors.IsNotFound(err) {
+ bpfProgram := &bpfmaniov1alpha1.BpfProgram{}
+ if err := r.Get(ctx, req.NamespacedName, bpfProgram); err != nil {
+ if errors.IsNotFound(err) {
+ r.Logger.V(1).Info("bpfProgram not found stale reconcile, exiting", "Name", req.NamespacedName)
+ } else {
+ r.Logger.Error(err, "failed getting bpfProgram Object", "Name", req.NamespacedName)
+ }
+ return ctrl.Result{}, nil
+ }
+
+ // Get owning FentryProgram object from ownerRef
+ ownerRef := metav1.GetControllerOf(bpfProgram)
+ if ownerRef == nil {
+ return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfProgram Object owner")
+ }
+
+ if err := r.Get(ctx, types.NamespacedName{Namespace: corev1.NamespaceAll, Name: ownerRef.Name}, fentryProgram); err != nil {
+ if errors.IsNotFound(err) {
+ r.Logger.Info("Fentry Program from ownerRef not found stale reconcile exiting", "Name", req.NamespacedName)
+ } else {
+ r.Logger.Error(err, "failed getting FentryProgram Object from ownerRef", "Name", req.NamespacedName)
+ }
+ return ctrl.Result{}, nil
+ }
+
+ } else {
+ r.Logger.Error(err, "failed getting FentryProgram Object", "Name", req.NamespacedName)
+ return ctrl.Result{}, nil
+ }
+ }
+
+ return reconcileBpfProgram(ctx, r, fentryProgram)
+}
+
+func (r *FentryProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) {
+ // Sometimes we end up with a stale FentryProgram due to races, do this
+ // get to ensure we're up to date before attempting a status update.
+ prog := &bpfmaniov1alpha1.FentryProgram{}
+ if err := r.Get(ctx, types.NamespacedName{Namespace: corev1.NamespaceAll, Name: name}, prog); err != nil {
+ r.Logger.V(1).Info("failed to get fresh FentryProgram object...requeuing")
+ return ctrl.Result{Requeue: true, RequeueAfter: retryDurationOperator}, nil
+ }
+
+ return r.ReconcilerCommon.updateCondition(ctx, prog, &prog.Status.Conditions, cond, message)
+}
diff --git a/bpfman-operator/controllers/bpfman-operator/fentry-program_test.go b/bpfman-operator/controllers/bpfman-operator/fentry-program_test.go
new file mode 100644
index 000000000..91e4cd764
--- /dev/null
+++ b/bpfman-operator/controllers/bpfman-operator/fentry-program_test.go
@@ -0,0 +1,181 @@
+/*
+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/bpfman-operator/apis/v1alpha1"
+ internal "github.com/bpfman/bpfman/bpfman-operator/internal"
+ testutils "github.com/bpfman/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 FentryProgramReconcile test. If multiCondition == true, it runs it
+// with an error case in which the program object has multiple conditions.
+func fentryProgramReconcile(t *testing.T, multiCondition bool) {
+ var (
+ name = "fakeFentryProgram"
+ bytecodePath = "/tmp/hello.o"
+ bpfFunctionName = "fentry_test"
+ fakeNode = testutils.NewNode("fake-control-plane")
+ functionName = "do_unlinkat"
+ ctx = context.TODO()
+ bpfProgName = fmt.Sprintf("%s-%s", name, fakeNode.Name)
+ )
+ // A FentryProgram object with metadata and spec.
+ Fentry := &bpfmaniov1alpha1.FentryProgram{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ },
+ Spec: bpfmaniov1alpha1.FentryProgramSpec{
+ BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{
+ BpfFunctionName: bpfFunctionName,
+ NodeSelector: metav1.LabelSelector{},
+ ByteCode: bpfmaniov1alpha1.BytecodeSelector{
+ Path: &bytecodePath,
+ },
+ },
+ FunctionName: functionName,
+ },
+ }
+
+ // The expected accompanying BpfProgram object
+ expectedBpfProg := &bpfmaniov1alpha1.BpfProgram{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: bpfProgName,
+ OwnerReferences: []metav1.OwnerReference{
+ {
+ Name: Fentry.Name,
+ Controller: &[]bool{true}[0],
+ },
+ },
+ Labels: map[string]string{internal.BpfProgramOwnerLabel: Fentry.Name, internal.K8sHostLabel: fakeNode.Name},
+ Finalizers: []string{internal.FentryProgramControllerFinalizer},
+ },
+ Spec: bpfmaniov1alpha1.BpfProgramSpec{
+ Type: "fentry",
+ },
+ Status: bpfmaniov1alpha1.BpfProgramStatus{
+ Conditions: []metav1.Condition{bpfmaniov1alpha1.BpfProgCondLoaded.Condition()},
+ },
+ }
+
+ // Objects to track in the fake client.
+ objs := []runtime.Object{fakeNode, Fentry, expectedBpfProg}
+
+ // Register operator types with the runtime scheme.
+ s := scheme.Scheme
+ s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, Fentry)
+ s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.TcProgramList{})
+ s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgram{})
+ s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgramList{})
+
+ // Create a fake client to mock API calls.
+ cl := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build()
+
+ rc := ReconcilerCommon{
+ Client: cl,
+ Scheme: s,
+ }
+
+ // Set development Logger so we can see all logs in tests.
+ logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true})))
+
+ // Create a FentryProgram object with the scheme and fake client.
+ r := &FentryProgramReconciler{ReconcilerCommon: rc}
+
+ // Mock request to simulate Reconcile() being called on an event for a
+ // watched resource .
+ req := reconcile.Request{
+ NamespacedName: types.NamespacedName{
+ Name: name,
+ },
+ }
+
+ // First reconcile should add the finalzier to the fentryProgram object
+ res, err := r.Reconcile(ctx, req)
+ if err != nil {
+ t.Fatalf("reconcile: (%v)", err)
+ }
+
+ // Require no requeue
+ require.False(t, res.Requeue)
+
+ // Check the BpfProgram Object was created successfully
+ err = cl.Get(ctx, types.NamespacedName{Name: Fentry.Name, Namespace: metav1.NamespaceAll}, Fentry)
+ require.NoError(t, err)
+
+ // Check the bpfman-operator finalizer was successfully added
+ require.Contains(t, Fentry.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(&Fentry.Status.Conditions, bpfmaniov1alpha1.ProgramDeleteError.Condition("bogus condition #1"))
+ if err := r.Status().Update(ctx, Fentry); err != nil {
+ r.Logger.V(1).Info("failed to set FentryProgram object status")
+ }
+ meta.SetStatusCondition(&Fentry.Status.Conditions, bpfmaniov1alpha1.ProgramReconcileError.Condition("bogus condition #2"))
+ if err := r.Status().Update(ctx, Fentry); err != nil {
+ r.Logger.V(1).Info("failed to set FentryProgram object status")
+ }
+ // Make sure we have 2 conditions
+ require.Equal(t, 2, len(Fentry.Status.Conditions))
+ }
+
+ // Second reconcile should check bpfProgram Status and write Success condition to tcProgram Status
+ res, err = r.Reconcile(ctx, req)
+ if err != nil {
+ t.Fatalf("reconcile: (%v)", err)
+ }
+
+ // Require no requeue
+ require.False(t, res.Requeue)
+
+ // Check the BpfProgram Object was created successfully
+ err = cl.Get(ctx, types.NamespacedName{Name: Fentry.Name, Namespace: metav1.NamespaceAll}, Fentry)
+ require.NoError(t, err)
+
+ // Make sure we only have 1 condition now
+ require.Equal(t, 1, len(Fentry.Status.Conditions))
+ // Make sure it's the right one.
+ require.Equal(t, Fentry.Status.Conditions[0].Type, string(bpfmaniov1alpha1.ProgramReconcileSuccess))
+}
+
+func TestFentryProgramReconcile(t *testing.T) {
+ fentryProgramReconcile(t, false)
+}
+
+func TestFentryUpdateStatus(t *testing.T) {
+ fentryProgramReconcile(t, true)
+}
diff --git a/bpfman-operator/controllers/bpfman-operator/fexit-program.go b/bpfman-operator/controllers/bpfman-operator/fexit-program.go
new file mode 100644
index 000000000..6739ed10c
--- /dev/null
+++ b/bpfman-operator/controllers/bpfman-operator/fexit-program.go
@@ -0,0 +1,116 @@
+/*
+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"
+
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/types"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/builder"
+
+ "sigs.k8s.io/controller-runtime/pkg/handler"
+ "sigs.k8s.io/controller-runtime/pkg/log"
+ "sigs.k8s.io/controller-runtime/pkg/predicate"
+ "sigs.k8s.io/controller-runtime/pkg/source"
+
+ bpfmaniov1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1"
+ "github.com/bpfman/bpfman/bpfman-operator/internal"
+)
+
+//+kubebuilder:rbac:groups=bpfman.io,resources=fexitprograms,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=bpfman.io,resources=fexitprograms/status,verbs=get;update;patch
+//+kubebuilder:rbac:groups=bpfman.io,resources=fexitprograms/finalizers,verbs=update
+
+type FexitProgramReconciler struct {
+ ReconcilerCommon
+}
+
+func (r *FexitProgramReconciler) getRecCommon() *ReconcilerCommon {
+ return &r.ReconcilerCommon
+}
+
+func (r *FexitProgramReconciler) getFinalizer() string {
+ return internal.FexitProgramControllerFinalizer
+}
+
+// SetupWithManager sets up the controller with the Manager.
+func (r *FexitProgramReconciler) SetupWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&bpfmaniov1alpha1.FexitProgram{}).
+ // Watch bpfPrograms which are owned by FexitPrograms
+ Watches(
+ &source.Kind{Type: &bpfmaniov1alpha1.BpfProgram{}},
+ &handler.EnqueueRequestForObject{},
+ builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.Tracing.String()))),
+ ).
+ Complete(r)
+}
+
+func (r *FexitProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ r.Logger = log.FromContext(ctx)
+
+ fexitProgram := &bpfmaniov1alpha1.FexitProgram{}
+ if err := r.Get(ctx, req.NamespacedName, fexitProgram); err != nil {
+ // Reconcile was triggered by bpfProgram event, get parent FexitProgram Object.
+ if errors.IsNotFound(err) {
+ bpfProgram := &bpfmaniov1alpha1.BpfProgram{}
+ if err := r.Get(ctx, req.NamespacedName, bpfProgram); err != nil {
+ if errors.IsNotFound(err) {
+ r.Logger.V(1).Info("bpfProgram not found stale reconcile, exiting", "Name", req.NamespacedName)
+ } else {
+ r.Logger.Error(err, "failed getting bpfProgram Object", "Name", req.NamespacedName)
+ }
+ return ctrl.Result{}, nil
+ }
+
+ // Get owning FexitProgram object from ownerRef
+ ownerRef := metav1.GetControllerOf(bpfProgram)
+ if ownerRef == nil {
+ return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfProgram Object owner")
+ }
+
+ if err := r.Get(ctx, types.NamespacedName{Namespace: corev1.NamespaceAll, Name: ownerRef.Name}, fexitProgram); err != nil {
+ if errors.IsNotFound(err) {
+ r.Logger.Info("Fexit Program from ownerRef not found stale reconcile exiting", "Name", req.NamespacedName)
+ } else {
+ r.Logger.Error(err, "failed getting FexitProgram Object from ownerRef", "Name", req.NamespacedName)
+ }
+ return ctrl.Result{}, nil
+ }
+
+ } else {
+ r.Logger.Error(err, "failed getting FexitProgram Object", "Name", req.NamespacedName)
+ return ctrl.Result{}, nil
+ }
+ }
+
+ return reconcileBpfProgram(ctx, r, fexitProgram)
+}
+
+func (r *FexitProgramReconciler) updateStatus(ctx context.Context, 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{}
+ if err := r.Get(ctx, types.NamespacedName{Namespace: corev1.NamespaceAll, Name: name}, prog); err != nil {
+ r.Logger.V(1).Info("failed to get fresh FexitProgram object...requeuing")
+ return ctrl.Result{Requeue: true, RequeueAfter: retryDurationOperator}, nil
+ }
+
+ return r.ReconcilerCommon.updateCondition(ctx, prog, &prog.Status.Conditions, cond, message)
+}
diff --git a/bpfman-operator/controllers/bpfman-operator/fexit-program_test.go b/bpfman-operator/controllers/bpfman-operator/fexit-program_test.go
new file mode 100644
index 000000000..9ed9caac8
--- /dev/null
+++ b/bpfman-operator/controllers/bpfman-operator/fexit-program_test.go
@@ -0,0 +1,181 @@
+/*
+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/bpfman-operator/apis/v1alpha1"
+ internal "github.com/bpfman/bpfman/bpfman-operator/internal"
+ testutils "github.com/bpfman/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 FexitProgramReconcile test. If multiCondition == true, it runs it
+// with an error case in which the program object has multiple conditions.
+func fexitProgramReconcile(t *testing.T, multiCondition bool) {
+ var (
+ name = "fakeFexitProgram"
+ bytecodePath = "/tmp/hello.o"
+ bpfFunctionName = "fexit_test"
+ fakeNode = testutils.NewNode("fake-control-plane")
+ functionName = "do_unlinkat"
+ ctx = context.TODO()
+ bpfProgName = fmt.Sprintf("%s-%s", name, fakeNode.Name)
+ )
+ // A FexitProgram object with metadata and spec.
+ Fexit := &bpfmaniov1alpha1.FexitProgram{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ },
+ Spec: bpfmaniov1alpha1.FexitProgramSpec{
+ BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{
+ BpfFunctionName: bpfFunctionName,
+ NodeSelector: metav1.LabelSelector{},
+ ByteCode: bpfmaniov1alpha1.BytecodeSelector{
+ Path: &bytecodePath,
+ },
+ },
+ FunctionName: functionName,
+ },
+ }
+
+ // The expected accompanying BpfProgram object
+ expectedBpfProg := &bpfmaniov1alpha1.BpfProgram{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: bpfProgName,
+ OwnerReferences: []metav1.OwnerReference{
+ {
+ Name: Fexit.Name,
+ Controller: &[]bool{true}[0],
+ },
+ },
+ Labels: map[string]string{internal.BpfProgramOwnerLabel: Fexit.Name, internal.K8sHostLabel: fakeNode.Name},
+ Finalizers: []string{internal.FexitProgramControllerFinalizer},
+ },
+ Spec: bpfmaniov1alpha1.BpfProgramSpec{
+ Type: "fexit",
+ },
+ Status: bpfmaniov1alpha1.BpfProgramStatus{
+ Conditions: []metav1.Condition{bpfmaniov1alpha1.BpfProgCondLoaded.Condition()},
+ },
+ }
+
+ // Objects to track in the fake client.
+ objs := []runtime.Object{fakeNode, Fexit, expectedBpfProg}
+
+ // Register operator types with the runtime scheme.
+ s := scheme.Scheme
+ s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, Fexit)
+ s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.TcProgramList{})
+ s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgram{})
+ s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgramList{})
+
+ // Create a fake client to mock API calls.
+ cl := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build()
+
+ rc := ReconcilerCommon{
+ Client: cl,
+ Scheme: s,
+ }
+
+ // Set development Logger so we can see all logs in tests.
+ logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true})))
+
+ // Create a FexitProgram object with the scheme and fake client.
+ r := &FexitProgramReconciler{ReconcilerCommon: rc}
+
+ // Mock request to simulate Reconcile() being called on an event for a
+ // watched resource .
+ req := reconcile.Request{
+ NamespacedName: types.NamespacedName{
+ Name: name,
+ },
+ }
+
+ // First reconcile should add the finalzier to the fexitProgram object
+ res, err := r.Reconcile(ctx, req)
+ if err != nil {
+ t.Fatalf("reconcile: (%v)", err)
+ }
+
+ // Require no requeue
+ require.False(t, res.Requeue)
+
+ // Check the BpfProgram Object was created successfully
+ err = cl.Get(ctx, types.NamespacedName{Name: Fexit.Name, Namespace: metav1.NamespaceAll}, Fexit)
+ require.NoError(t, err)
+
+ // Check the bpfman-operator finalizer was successfully added
+ require.Contains(t, Fexit.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(&Fexit.Status.Conditions, bpfmaniov1alpha1.ProgramDeleteError.Condition("bogus condition #1"))
+ if err := r.Status().Update(ctx, Fexit); err != nil {
+ r.Logger.V(1).Info("failed to set FexitProgram object status")
+ }
+ meta.SetStatusCondition(&Fexit.Status.Conditions, bpfmaniov1alpha1.ProgramReconcileError.Condition("bogus condition #2"))
+ if err := r.Status().Update(ctx, Fexit); err != nil {
+ r.Logger.V(1).Info("failed to set FexitProgram object status")
+ }
+ // Make sure we have 2 conditions
+ require.Equal(t, 2, len(Fexit.Status.Conditions))
+ }
+
+ // Second reconcile should check bpfProgram Status and write Success condition to tcProgram Status
+ res, err = r.Reconcile(ctx, req)
+ if err != nil {
+ t.Fatalf("reconcile: (%v)", err)
+ }
+
+ // Require no requeue
+ require.False(t, res.Requeue)
+
+ // Check the BpfProgram Object was created successfully
+ err = cl.Get(ctx, types.NamespacedName{Name: Fexit.Name, Namespace: metav1.NamespaceAll}, Fexit)
+ require.NoError(t, err)
+
+ // Make sure we only have 1 condition now
+ require.Equal(t, 1, len(Fexit.Status.Conditions))
+ // Make sure it's the right one.
+ require.Equal(t, Fexit.Status.Conditions[0].Type, string(bpfmaniov1alpha1.ProgramReconcileSuccess))
+}
+
+func TestFexitProgramReconcile(t *testing.T) {
+ fexitProgramReconcile(t, false)
+}
+
+func TestFexitUpdateStatus(t *testing.T) {
+ fexitProgramReconcile(t, true)
+}
diff --git a/bpfman-operator/controllers/bpfman-operator/kprobe-program_test.go b/bpfman-operator/controllers/bpfman-operator/kprobe-program_test.go
index fb8d148e0..2612c1306 100644
--- a/bpfman-operator/controllers/bpfman-operator/kprobe-program_test.go
+++ b/bpfman-operator/controllers/bpfman-operator/kprobe-program_test.go
@@ -65,9 +65,9 @@ func kprobeProgramReconcile(t *testing.T, multiCondition bool) {
Path: &bytecodePath,
},
},
- FunctionNames: []string{functionName},
- Offset: uint64(offset),
- RetProbe: retprobe,
+ FunctionName: functionName,
+ Offset: uint64(offset),
+ RetProbe: retprobe,
},
}
@@ -180,6 +180,6 @@ func TestKprobeProgramReconcile(t *testing.T) {
kprobeProgramReconcile(t, false)
}
-func TestUpdateStatus(t *testing.T) {
+func TestKprobeUpdateStatus(t *testing.T) {
kprobeProgramReconcile(t, true)
}
diff --git a/bpfman-operator/controllers/bpfman-operator/uprobe-program_test.go b/bpfman-operator/controllers/bpfman-operator/uprobe-program_test.go
index 2161c80e3..6a91105ac 100644
--- a/bpfman-operator/controllers/bpfman-operator/uprobe-program_test.go
+++ b/bpfman-operator/controllers/bpfman-operator/uprobe-program_test.go
@@ -64,7 +64,7 @@ func TestUprobeProgramReconcile(t *testing.T) {
},
},
FunctionName: functionName,
- Targets: []string{target},
+ Target: target,
Offset: uint64(offset),
RetProbe: retprobe,
},
diff --git a/bpfman-operator/hack/build-release-yamls.sh b/bpfman-operator/hack/build-release-yamls.sh
index 35d9fb5b5..5ff02e6b9 100755
--- a/bpfman-operator/hack/build-release-yamls.sh
+++ b/bpfman-operator/hack/build-release-yamls.sh
@@ -20,7 +20,7 @@ set -o pipefail
thisyear=`date +"%Y"`
-mkdir -p release/
+mkdir -p release-v${VERSION}/
## Location to install dependencies to
LOCALBIN=$(pwd)/bin
@@ -33,9 +33,9 @@ KUSTOMIZE=${LOCALBIN}/kustomize
## 1. bpfman CRD install
# Make clean files with boilerplate
-cat hack/boilerplate.sh.txt > release/bpfman-crds-install-v${VERSION}.yaml
-sed -i "s/YEAR/$thisyear/g" release/bpfman-crds-install-v${VERSION}.yaml
-cat << EOF >> release/bpfman-crds-install-v${VERSION}.yaml
+cat hack/boilerplate.sh.txt > release-v${VERSION}/bpfman-crds-install.yaml
+sed -i "s/YEAR/$thisyear/g" release-v${VERSION}/bpfman-crds-install.yaml
+cat << EOF >> release-v${VERSION}/bpfman-crds-install.yaml
#
# bpfman Kubernetes API install
#
@@ -43,44 +43,56 @@ EOF
for file in `ls config/crd/bases/bpfman*.yaml`
do
- echo "---" >> release/bpfman-crds-install-v${VERSION}.yaml
- echo "#" >> release/bpfman-crds-install-v${VERSION}.yaml
- echo "# $file" >> release/bpfman-crds-install-v${VERSION}.yaml
- echo "#" >> release/bpfman-crds-install-v${VERSION}.yaml
- cat $file >> release/bpfman-crds-install-v${VERSION}.yaml
+ echo "---" >> release-v${VERSION}/bpfman-crds-install.yaml
+ echo "#" >> release-v${VERSION}/bpfman-crds-install.yaml
+ echo "# $file" >> release-v${VERSION}/bpfman-crds-install.yaml
+ echo "#" >> release-v${VERSION}/bpfman-crds-install.yaml
+ cat $file >> release-v${VERSION}/bpfman-crds-install.yaml
done
-echo "Generated:" release/bpfman-crds-install-v${VERSION}.yaml
+echo "Generated:" release-v${VERSION}/bpfman-crds-install.yaml
## 2. bpfman-operator install yaml
$(cd ./config/bpfman-operator-deployment && ${KUSTOMIZE} edit set image quay.io/bpfman/bpfman-operator=quay.io/bpfman/bpfman-operator:v${VERSION})
-${KUSTOMIZE} build ./config/default > release/bpfman-operator-install-v${VERSION}.yaml
+${KUSTOMIZE} build ./config/default > release-v${VERSION}/bpfman-operator-install.yaml
### replace configmap :latest images with :v${VERSION}
-sed -i "s/quay.io\/bpfman\/bpfman-agent:latest/quay.io\/bpfman\/bpfman-agent:v${VERSION}/g" release/bpfman-operator-install-v${VERSION}.yaml
-sed -i "s/quay.io\/bpfman\/bpfman:latest/quay.io\/bpfman\/bpfman:v${VERSION}/g" release/bpfman-operator-install-v${VERSION}.yaml
+sed -i "s/quay.io\/bpfman\/bpfman-agent:latest/quay.io\/bpfman\/bpfman-agent:v${VERSION}/g" release-v${VERSION}/bpfman-operator-install.yaml
+sed -i "s/quay.io\/bpfman\/bpfman:latest/quay.io\/bpfman\/bpfman:v${VERSION}/g" release-v${VERSION}/bpfman-operator-install.yaml
-echo "Generated:" release/bpfman-operator-install-v${VERSION}.yaml
+echo "Generated:" release-v${VERSION}/bpfman-operator-install.yaml
## 3. examples install yamls
### XDP
-${KUSTOMIZE} build ../examples/config/v${VERSION}/go-xdp-counter > release/go-xdp-counter-install-v${VERSION}.yaml
-echo "Generated:" go-xdp-counter-install-v${VERSION}.yaml
+${KUSTOMIZE} build ../examples/config/v${VERSION}/go-xdp-counter > release-v${VERSION}/go-xdp-counter-install.yaml
+echo "Generated:" release-v${VERSION}/go-xdp-counter-install.yaml
### TC
-${KUSTOMIZE} build ../examples/config/v${VERSION}/go-tc-counter > release/go-tc-counter-install-v${VERSION}.yaml
-echo "Generated:" go-tc-counter-install-v${VERSION}.yaml
+${KUSTOMIZE} build ../examples/config/v${VERSION}/go-tc-counter > release-v${VERSION}/go-tc-counter-install.yaml
+echo "Generated:" release-v${VERSION}/go-tc-counter-install.yaml
### TRACEPOINT
-${KUSTOMIZE} build ../examples/config/v${VERSION}/go-tracepoint-counter > release/go-tracepoint-counter-install-v${VERSION}.yaml
-echo "Generated:" go-tracepoint-counter-install-v${VERSION}.yaml
+${KUSTOMIZE} build ../examples/config/v${VERSION}/go-tracepoint-counter > release-v${VERSION}/go-tracepoint-counter-install.yaml
+echo "Generated:" release-v${VERSION}/go-tracepoint-counter-install.yaml
+### UPROBE
+${KUSTOMIZE} build ../examples/config/v${VERSION}/go-uprobe-counter > release-v${VERSION}/go-uprobe-counter-install.yaml
+echo "Generated:" release-v${VERSION}/go-uprobe-counter-install.yaml
+### KPROBE
+${KUSTOMIZE} build ../examples/config/v${VERSION}/go-kprobe-counter > release-v${VERSION}/go-kprobe-counter-install.yaml
+echo "Generated:" release-v${VERSION}/go-kprobe-counter-install.yaml
## 4. examples install yamls for OCP
### XDP
-${KUSTOMIZE} build ../examples/config/v${VERSION}-ocp/go-xdp-counter > release/go-xdp-counter-install-ocp-v${VERSION}.yaml
-echo "Generated:" go-xdp-counter-install-ocp-v${VERSION}.yaml
+${KUSTOMIZE} build ../examples/config/v${VERSION}-ocp/go-xdp-counter > release-v${VERSION}/go-xdp-counter-install-ocp.yaml
+echo "Generated:" release-v${VERSION}/go-xdp-counter-install-ocp.yaml
### TC
-${KUSTOMIZE} build ../examples/config/v${VERSION}-ocp/go-tc-counter > release/go-tc-counter-install-ocp-v${VERSION}.yaml
-echo "Generated:" go-tc-counter-install-ocp-v${VERSION}.yaml
+${KUSTOMIZE} build ../examples/config/v${VERSION}-ocp/go-tc-counter > release-v${VERSION}/go-tc-counter-install-ocp.yaml
+echo "Generated:" release-v${VERSION}/go-tc-counter-install-ocp.yaml
### TRACEPOINT
-${KUSTOMIZE} build ../examples/config/v${VERSION}-ocp/go-tracepoint-counter > release/go-tracepoint-counter-install-ocp-v${VERSION}.yaml
-echo "Generated:" go-tracepoint-counter-install-ocp-v${VERSION}.yaml
+${KUSTOMIZE} build ../examples/config/v${VERSION}-ocp/go-tracepoint-counter > release-v${VERSION}/go-tracepoint-counter-install-ocp.yaml
+echo "Generated:" release-v${VERSION}/go-tracepoint-counter-install-ocp.yaml
+### UPROBE
+${KUSTOMIZE} build ../examples/config/v${VERSION}-ocp/go-uprobe-counter > release-v${VERSION}/go-uprobe-counter-install-ocp.yaml
+echo "Generated:" release-v${VERSION}/go-uprobe-counter-install-ocp.yaml
+### KPROBE
+${KUSTOMIZE} build ../examples/config/v${VERSION}-ocp/go-kprobe-counter > release-v${VERSION}/go-kprobe-counter-install-ocp.yaml
+echo "Generated:" release-v${VERSION}/go-kprobe-counter-install-ocp.yaml
diff --git a/bpfman-operator/hack/kubectl-bpfprogramconfigs b/bpfman-operator/hack/kubectl-bpfprogramconfigs
deleted file mode 100755
index a73cf8f78..000000000
--- a/bpfman-operator/hack/kubectl-bpfprogramconfigs
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-kubectl get bpfprogramconfigs -o custom-columns="NAME":.metadata.name,"TYPE":.spec.type,"SECNAME":.spec.name,"STATUS":.status.conditions[-1:].type,"INTERFACE":.spec.attachpoint.networkmultiattach.interfaceselector.interface,"PRIORITY":.spec.attachpoint.networkmultiattach.priority,"DIRECTION":.spec.attachpoint.networkmultiattach.direction,"TRACEPOINT":.spec.attachpoint.singleattach.name
diff --git a/bpfman-operator/hack/kubectl-bpfprograms b/bpfman-operator/hack/kubectl-bpfprograms
deleted file mode 100755
index 72b6e6e48..000000000
--- a/bpfman-operator/hack/kubectl-bpfprograms
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-kubectl get bpfprograms -o jsonpath='{"\n"}{"STATUS\tREASON\t\tNAME\n IFACE\tPRI\tDIR\tTRACEPOINT\n\n"}{range .items[*]}{range .status.conditions[-1:]}{.type}{"\t"}{.reason}{end}{"\t"}{.metadata.name}{"\n"}{range .spec.programs.*}{" "}{.attachpoint.networkmultiattach.interfaceselector.interface}{"\t"}{.attachpoint.networkmultiattach.priority}{"\t"}{.attachpoint.networkmultiattach.direction}{"\t"}{.attachpoint.singleattach.name}{end}{"\n"}{"\n"}{end}'
diff --git a/bpfman-operator/hack/nginx-deployment.yaml b/bpfman-operator/hack/nginx-deployment.yaml
new file mode 100644
index 000000000..656ddc322
--- /dev/null
+++ b/bpfman-operator/hack/nginx-deployment.yaml
@@ -0,0 +1,19 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: nginx-deployment
+spec:
+ selector:
+ matchLabels:
+ app: nginx
+ replicas: 2 # tells deployment to run 2 pods matching the template
+ template:
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ containers:
+ - name: nginx
+ image: nginx:latest
+ ports:
+ - containerPort: 80
diff --git a/bpfman-operator/internal/conn/conn.go b/bpfman-operator/internal/conn/conn.go
index 2737e815e..2f28f257a 100644
--- a/bpfman-operator/internal/conn/conn.go
+++ b/bpfman-operator/internal/conn/conn.go
@@ -19,81 +19,20 @@ package conn
import (
"context"
"fmt"
- "io"
- "os"
"github.com/bpfman/bpfman/bpfman-operator/internal"
- toml "github.com/pelletier/go-toml"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
- ctrl "sigs.k8s.io/controller-runtime"
)
-var log = ctrl.Log.WithName("bpfman-conn")
+//var log = ctrl.Log.WithName("bpfman-conn")
-type Endpoint struct {
- Type string `toml:"type"`
- Path string `toml:"path"`
- Port uint16 `toml:"port"`
- Enabled bool `toml:"enabled"`
-}
-
-type Grpc struct {
- Endpoints []Endpoint `toml:"endpoints"`
-}
-
-type ConfigFileData struct {
- Grpc Grpc `toml:"grpc"`
-}
-
-func LoadConfig() ConfigFileData {
- config := ConfigFileData{}
-
- log.Info("Reading...\n", "Default config path", internal.DefaultConfigPath)
- file, err := os.Open(internal.DefaultConfigPath)
+func CreateConnection(ctx context.Context, creds credentials.TransportCredentials) (*grpc.ClientConn, error) {
+ addr := fmt.Sprintf("unix://%s", internal.DefaultPath)
+ conn, err := grpc.DialContext(ctx, addr, grpc.WithTransportCredentials(creds), grpc.WithBlock())
if err != nil {
- panic(err)
- }
-
- b, err := io.ReadAll(file)
- if err == nil {
- err = toml.Unmarshal(b, &config)
- if err != nil {
- log.Info("Unmarshal failed: err %+v\n", err)
- }
- } else {
- log.Info("Read config-path failed: err\n", "config-path", internal.DefaultConfigPath, "err", err)
- }
-
- return config
-}
-
-func CreateConnection(endpoints []Endpoint, ctx context.Context, creds credentials.TransportCredentials) (*grpc.ClientConn, error) {
- var (
- addr string
- )
-
- // TODO(astoycos) this currently connects to the first valid endpoint
- // rather then spawning multiple connections. This should be cleaned up
- // and made explicitly configurable.
- for _, e := range endpoints {
- if !e.Enabled {
- continue
- }
-
- if e.Type == "tcp" {
- addr = fmt.Sprintf("localhost:%d", e.Port)
- } else if e.Type == "unix" {
- addr = fmt.Sprintf("unix://%s", e.Path)
- }
-
- conn, err := grpc.DialContext(ctx, addr, grpc.WithTransportCredentials(creds), grpc.WithBlock())
- if err != nil {
- return nil, fmt.Errorf("unable to establish connection to %s: %w", addr, err)
- }
-
- return conn, nil
+ return nil, fmt.Errorf("unable to establish connection to %s: %w", addr, err)
}
- return nil, fmt.Errorf("unable to establish connection, no valid endpoints")
+ return conn, nil
}
diff --git a/bpfman-operator/internal/constants.go b/bpfman-operator/internal/constants.go
index 90953b824..a0d14b253 100644
--- a/bpfman-operator/internal/constants.go
+++ b/bpfman-operator/internal/constants.go
@@ -24,6 +24,10 @@ const (
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"
BpfProgramOwnerLabel = "bpfman.io/ownedByProgram"
K8sHostLabel = "kubernetes.io/hostname"
DiscoveredLabel = "bpfman.io/discoveredProgram"
@@ -35,12 +39,13 @@ const (
BpfmanDsName = "bpfman-daemon"
BpfmanConfigName = "bpfman-config"
BpfmanCsiDriverName = "csi.bpfman.io"
+ BpfmanContainerName = "bpfman"
+ BpfmanAgentContainerName = "bpfman-agent"
BpfmanDaemonManifestPath = "./config/bpfman-deployment/daemonset.yaml"
BpfmanCsiDriverPath = "./config/bpfman-deployment/csidriverinfo.yaml"
BpfmanMapFs = "/run/bpfman/fs/maps"
- DefaultConfigPath = "/etc/bpfman/bpfman.toml"
DefaultType = "tcp"
- DefaultPath = "/run/bpfman/bpfman.sock"
+ DefaultPath = "/run/bpfman-sock/bpfman.sock"
DefaultPort = 50051
DefaultEnabled = true
)
@@ -65,9 +70,15 @@ const (
// KprobeProgramControllerFinalizer is the finalizer that holds a Kprobe
// BpfProgram object from deletion until cleanup can be performed.
KprobeProgramControllerFinalizer = "bpfman.io.kprobeprogramcontroller/finalizer"
- // KprobeProgramControllerFinalizer is the finalizer that holds a Uprobe
+ // UprobeProgramControllerFinalizer is the finalizer that holds a Uprobe
// BpfProgram object from deletion until cleanup can be performed.
UprobeProgramControllerFinalizer = "bpfman.io.uprobeprogramcontroller/finalizer"
+ // FentryProgramControllerFinalizer is the finalizer that holds a Fentry
+ // BpfProgram object from deletion until cleanup can be performed.
+ FentryProgramControllerFinalizer = "bpfman.io.fentryprogramcontroller/finalizer"
+ // FexitProgramControllerFinalizer is the finalizer that holds a Fexit
+ // BpfProgram object from deletion until cleanup can be performed.
+ FexitProgramControllerFinalizer = "bpfman.io.fexitprogramcontroller/finalizer"
)
// Must match the kernel's `bpf_prog_type` enum.
@@ -127,6 +138,10 @@ func FromString(p string) (*ProgramType, error) {
programType = Kprobe
case "uprobe":
programType = Kprobe
+ case "fentry":
+ programType = Tracing
+ case "fexit":
+ programType = Tracing
default:
return nil, fmt.Errorf("unknown program type: %s", p)
}
diff --git a/bpfman-operator/pkg/client/clientset/versioned/clientset.go b/bpfman-operator/pkg/client/clientset/versioned/clientset.go
index e20c15d71..9cfe012e5 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/clientset.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/clientset.go
@@ -13,6 +13,7 @@ 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 versioned
@@ -32,7 +33,8 @@ type Interface interface {
BpfmanV1alpha1() bpfmanv1alpha1.BpfmanV1alpha1Interface
}
-// Clientset contains the clients for groups.
+// Clientset contains the clients for groups. Each group has exactly one
+// version included in a Clientset.
type Clientset struct {
*discovery.DiscoveryClient
bpfmanV1alpha1 *bpfmanv1alpha1.BpfmanV1alpha1Client
diff --git a/bpfman-operator/pkg/client/clientset/versioned/doc.go b/bpfman-operator/pkg/client/clientset/versioned/doc.go
index b7179e900..ce60fa421 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/doc.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/doc.go
@@ -13,6 +13,7 @@ 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.
// This package has the automatically generated clientset.
diff --git a/bpfman-operator/pkg/client/clientset/versioned/fake/clientset_generated.go b/bpfman-operator/pkg/client/clientset/versioned/fake/clientset_generated.go
index bd7a1c584..ecad40626 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/fake/clientset_generated.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/fake/clientset_generated.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/clientset/versioned/fake/doc.go b/bpfman-operator/pkg/client/clientset/versioned/fake/doc.go
index 1d2134ad6..f579368d7 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/fake/doc.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/fake/doc.go
@@ -13,6 +13,7 @@ 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.
// This package has the automatically generated fake clientset.
diff --git a/bpfman-operator/pkg/client/clientset/versioned/fake/register.go b/bpfman-operator/pkg/client/clientset/versioned/fake/register.go
index 320e47d99..d3dfad540 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/fake/register.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/fake/register.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/clientset/versioned/scheme/doc.go b/bpfman-operator/pkg/client/clientset/versioned/scheme/doc.go
index 19c6633f8..04ec2e1bf 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/scheme/doc.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/scheme/doc.go
@@ -13,6 +13,7 @@ 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.
// This package contains the scheme of the automatically generated clientset.
diff --git a/bpfman-operator/pkg/client/clientset/versioned/scheme/register.go b/bpfman-operator/pkg/client/clientset/versioned/scheme/register.go
index de8bf4d87..7d109c14f 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/scheme/register.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/scheme/register.go
@@ -13,6 +13,7 @@ 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 scheme
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go
index 7df4ac867..2934f7b01 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go
@@ -13,6 +13,7 @@ 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
@@ -28,6 +29,8 @@ import (
type BpfmanV1alpha1Interface interface {
RESTClient() rest.Interface
BpfProgramsGetter
+ FentryProgramsGetter
+ FexitProgramsGetter
KprobeProgramsGetter
TcProgramsGetter
TracepointProgramsGetter
@@ -44,6 +47,14 @@ func (c *BpfmanV1alpha1Client) BpfPrograms() BpfProgramInterface {
return newBpfPrograms(c)
}
+func (c *BpfmanV1alpha1Client) FentryPrograms() FentryProgramInterface {
+ return newFentryPrograms(c)
+}
+
+func (c *BpfmanV1alpha1Client) FexitPrograms() FexitProgramInterface {
+ return newFexitPrograms(c)
+}
+
func (c *BpfmanV1alpha1Client) KprobePrograms() KprobeProgramInterface {
return newKprobePrograms(c)
}
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/bpfprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/bpfprogram.go
index b3a94a48d..382a6fa13 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/bpfprogram.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/bpfprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/doc.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/doc.go
index a47d86894..d7981c096 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/doc.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/doc.go
@@ -13,6 +13,7 @@ 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.
// This package has the automatically generated typed clients.
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/doc.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/doc.go
index fe81f65a2..872443930 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/doc.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/doc.go
@@ -13,6 +13,7 @@ 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 has the automatically generated clients.
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go
index 065a2c6f4..bea6d7271 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go
@@ -13,6 +13,7 @@ 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
@@ -31,6 +32,14 @@ func (c *FakeBpfmanV1alpha1) BpfPrograms() v1alpha1.BpfProgramInterface {
return &FakeBpfPrograms{c}
}
+func (c *FakeBpfmanV1alpha1) FentryPrograms() v1alpha1.FentryProgramInterface {
+ return &FakeFentryPrograms{c}
+}
+
+func (c *FakeBpfmanV1alpha1) FexitPrograms() v1alpha1.FexitProgramInterface {
+ return &FakeFexitPrograms{c}
+}
+
func (c *FakeBpfmanV1alpha1) KprobePrograms() v1alpha1.KprobeProgramInterface {
return &FakeKprobePrograms{c}
}
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_bpfprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_bpfprogram.go
index 10e840857..fc762e31b 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_bpfprogram.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_bpfprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_fentryprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_fentryprogram.go
new file mode 100644
index 000000000..932db226f
--- /dev/null
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_fentryprogram.go
@@ -0,0 +1,133 @@
+/*
+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/bpfman-operator/apis/v1alpha1"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ labels "k8s.io/apimachinery/pkg/labels"
+ schema "k8s.io/apimachinery/pkg/runtime/schema"
+ types "k8s.io/apimachinery/pkg/types"
+ watch "k8s.io/apimachinery/pkg/watch"
+ testing "k8s.io/client-go/testing"
+)
+
+// FakeFentryPrograms implements FentryProgramInterface
+type FakeFentryPrograms struct {
+ Fake *FakeBpfmanV1alpha1
+}
+
+var fentryprogramsResource = schema.GroupVersionResource{Group: "bpfman.io", Version: "v1alpha1", Resource: "fentryprograms"}
+
+var fentryprogramsKind = schema.GroupVersionKind{Group: "bpfman.io", Version: "v1alpha1", Kind: "FentryProgram"}
+
+// Get takes name of the fentryProgram, and returns the corresponding fentryProgram object, and an error if there is any.
+func (c *FakeFentryPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.FentryProgram, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewRootGetAction(fentryprogramsResource, name), &v1alpha1.FentryProgram{})
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.FentryProgram), err
+}
+
+// List takes label and field selectors, and returns the list of FentryPrograms that match those selectors.
+func (c *FakeFentryPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.FentryProgramList, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewRootListAction(fentryprogramsResource, fentryprogramsKind, opts), &v1alpha1.FentryProgramList{})
+ if obj == nil {
+ return nil, err
+ }
+
+ label, _, _ := testing.ExtractFromListOptions(opts)
+ if label == nil {
+ label = labels.Everything()
+ }
+ list := &v1alpha1.FentryProgramList{ListMeta: obj.(*v1alpha1.FentryProgramList).ListMeta}
+ for _, item := range obj.(*v1alpha1.FentryProgramList).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 fentryPrograms.
+func (c *FakeFentryPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
+ return c.Fake.
+ InvokesWatch(testing.NewRootWatchAction(fentryprogramsResource, opts))
+}
+
+// Create takes the representation of a fentryProgram and creates it. Returns the server's representation of the fentryProgram, and an error, if there is any.
+func (c *FakeFentryPrograms) Create(ctx context.Context, fentryProgram *v1alpha1.FentryProgram, opts v1.CreateOptions) (result *v1alpha1.FentryProgram, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewRootCreateAction(fentryprogramsResource, fentryProgram), &v1alpha1.FentryProgram{})
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.FentryProgram), err
+}
+
+// Update takes the representation of a fentryProgram and updates it. Returns the server's representation of the fentryProgram, and an error, if there is any.
+func (c *FakeFentryPrograms) Update(ctx context.Context, fentryProgram *v1alpha1.FentryProgram, opts v1.UpdateOptions) (result *v1alpha1.FentryProgram, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewRootUpdateAction(fentryprogramsResource, fentryProgram), &v1alpha1.FentryProgram{})
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.FentryProgram), 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 *FakeFentryPrograms) UpdateStatus(ctx context.Context, fentryProgram *v1alpha1.FentryProgram, opts v1.UpdateOptions) (*v1alpha1.FentryProgram, error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewRootUpdateSubresourceAction(fentryprogramsResource, "status", fentryProgram), &v1alpha1.FentryProgram{})
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.FentryProgram), err
+}
+
+// Delete takes name of the fentryProgram and deletes it. Returns an error if one occurs.
+func (c *FakeFentryPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
+ _, err := c.Fake.
+ Invokes(testing.NewRootDeleteActionWithOptions(fentryprogramsResource, name, opts), &v1alpha1.FentryProgram{})
+ return err
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *FakeFentryPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
+ action := testing.NewRootDeleteCollectionAction(fentryprogramsResource, listOpts)
+
+ _, err := c.Fake.Invokes(action, &v1alpha1.FentryProgramList{})
+ return err
+}
+
+// Patch applies the patch and returns the patched fentryProgram.
+func (c *FakeFentryPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.FentryProgram, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewRootPatchSubresourceAction(fentryprogramsResource, name, pt, data, subresources...), &v1alpha1.FentryProgram{})
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.FentryProgram), err
+}
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_fexitprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_fexitprogram.go
new file mode 100644
index 000000000..81cb152bd
--- /dev/null
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_fexitprogram.go
@@ -0,0 +1,133 @@
+/*
+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/bpfman-operator/apis/v1alpha1"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ labels "k8s.io/apimachinery/pkg/labels"
+ schema "k8s.io/apimachinery/pkg/runtime/schema"
+ types "k8s.io/apimachinery/pkg/types"
+ watch "k8s.io/apimachinery/pkg/watch"
+ testing "k8s.io/client-go/testing"
+)
+
+// FakeFexitPrograms implements FexitProgramInterface
+type FakeFexitPrograms struct {
+ Fake *FakeBpfmanV1alpha1
+}
+
+var fexitprogramsResource = schema.GroupVersionResource{Group: "bpfman.io", Version: "v1alpha1", Resource: "fexitprograms"}
+
+var fexitprogramsKind = schema.GroupVersionKind{Group: "bpfman.io", Version: "v1alpha1", Kind: "FexitProgram"}
+
+// Get takes name of the fexitProgram, and returns the corresponding fexitProgram object, and an error if there is any.
+func (c *FakeFexitPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.FexitProgram, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewRootGetAction(fexitprogramsResource, name), &v1alpha1.FexitProgram{})
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.FexitProgram), err
+}
+
+// List takes label and field selectors, and returns the list of FexitPrograms that match those selectors.
+func (c *FakeFexitPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.FexitProgramList, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewRootListAction(fexitprogramsResource, fexitprogramsKind, opts), &v1alpha1.FexitProgramList{})
+ if obj == nil {
+ return nil, err
+ }
+
+ label, _, _ := testing.ExtractFromListOptions(opts)
+ if label == nil {
+ label = labels.Everything()
+ }
+ list := &v1alpha1.FexitProgramList{ListMeta: obj.(*v1alpha1.FexitProgramList).ListMeta}
+ for _, item := range obj.(*v1alpha1.FexitProgramList).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 fexitPrograms.
+func (c *FakeFexitPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
+ return c.Fake.
+ InvokesWatch(testing.NewRootWatchAction(fexitprogramsResource, opts))
+}
+
+// Create takes the representation of a fexitProgram and creates it. Returns the server's representation of the fexitProgram, and an error, if there is any.
+func (c *FakeFexitPrograms) Create(ctx context.Context, fexitProgram *v1alpha1.FexitProgram, opts v1.CreateOptions) (result *v1alpha1.FexitProgram, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewRootCreateAction(fexitprogramsResource, fexitProgram), &v1alpha1.FexitProgram{})
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.FexitProgram), err
+}
+
+// Update takes the representation of a fexitProgram and updates it. Returns the server's representation of the fexitProgram, and an error, if there is any.
+func (c *FakeFexitPrograms) Update(ctx context.Context, fexitProgram *v1alpha1.FexitProgram, opts v1.UpdateOptions) (result *v1alpha1.FexitProgram, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewRootUpdateAction(fexitprogramsResource, fexitProgram), &v1alpha1.FexitProgram{})
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.FexitProgram), 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 *FakeFexitPrograms) UpdateStatus(ctx context.Context, fexitProgram *v1alpha1.FexitProgram, opts v1.UpdateOptions) (*v1alpha1.FexitProgram, error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewRootUpdateSubresourceAction(fexitprogramsResource, "status", fexitProgram), &v1alpha1.FexitProgram{})
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.FexitProgram), err
+}
+
+// Delete takes name of the fexitProgram and deletes it. Returns an error if one occurs.
+func (c *FakeFexitPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
+ _, err := c.Fake.
+ Invokes(testing.NewRootDeleteActionWithOptions(fexitprogramsResource, name, opts), &v1alpha1.FexitProgram{})
+ return err
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *FakeFexitPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
+ action := testing.NewRootDeleteCollectionAction(fexitprogramsResource, listOpts)
+
+ _, err := c.Fake.Invokes(action, &v1alpha1.FexitProgramList{})
+ return err
+}
+
+// Patch applies the patch and returns the patched fexitProgram.
+func (c *FakeFexitPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.FexitProgram, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewRootPatchSubresourceAction(fexitprogramsResource, name, pt, data, subresources...), &v1alpha1.FexitProgram{})
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.FexitProgram), err
+}
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_kprobeprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_kprobeprogram.go
index f68c42b47..759f9dc47 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_kprobeprogram.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_kprobeprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_tcprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_tcprogram.go
index fab3b1caa..e7206e53f 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_tcprogram.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_tcprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_tracepointprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_tracepointprogram.go
index 24361afed..0c9c2af20 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_tracepointprogram.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_tracepointprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_uprobeprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_uprobeprogram.go
index 2d21bfe19..7133cf804 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_uprobeprogram.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_uprobeprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_xdpprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_xdpprogram.go
index 37ff9a11b..68d94aac6 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_xdpprogram.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_xdpprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fentryprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fentryprogram.go
new file mode 100644
index 000000000..b66f6a3e8
--- /dev/null
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fentryprogram.go
@@ -0,0 +1,184 @@
+/*
+Copyright 2023 The bpfman Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ "context"
+ "time"
+
+ v1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1"
+ scheme "github.com/bpfman/bpfman/bpfman-operator/pkg/client/clientset/versioned/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"
+)
+
+// FentryProgramsGetter has a method to return a FentryProgramInterface.
+// A group's client should implement this interface.
+type FentryProgramsGetter interface {
+ FentryPrograms() FentryProgramInterface
+}
+
+// FentryProgramInterface has methods to work with FentryProgram resources.
+type FentryProgramInterface interface {
+ Create(ctx context.Context, fentryProgram *v1alpha1.FentryProgram, opts v1.CreateOptions) (*v1alpha1.FentryProgram, error)
+ Update(ctx context.Context, fentryProgram *v1alpha1.FentryProgram, opts v1.UpdateOptions) (*v1alpha1.FentryProgram, error)
+ UpdateStatus(ctx context.Context, fentryProgram *v1alpha1.FentryProgram, opts v1.UpdateOptions) (*v1alpha1.FentryProgram, 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.FentryProgram, error)
+ List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.FentryProgramList, 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.FentryProgram, err error)
+ FentryProgramExpansion
+}
+
+// fentryPrograms implements FentryProgramInterface
+type fentryPrograms struct {
+ client rest.Interface
+}
+
+// newFentryPrograms returns a FentryPrograms
+func newFentryPrograms(c *BpfmanV1alpha1Client) *fentryPrograms {
+ return &fentryPrograms{
+ client: c.RESTClient(),
+ }
+}
+
+// Get takes name of the fentryProgram, and returns the corresponding fentryProgram object, and an error if there is any.
+func (c *fentryPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.FentryProgram, err error) {
+ result = &v1alpha1.FentryProgram{}
+ err = c.client.Get().
+ Resource("fentryprograms").
+ Name(name).
+ VersionedParams(&options, scheme.ParameterCodec).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// List takes label and field selectors, and returns the list of FentryPrograms that match those selectors.
+func (c *fentryPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.FentryProgramList, err error) {
+ var timeout time.Duration
+ if opts.TimeoutSeconds != nil {
+ timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+ }
+ result = &v1alpha1.FentryProgramList{}
+ err = c.client.Get().
+ Resource("fentryprograms").
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Timeout(timeout).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// Watch returns a watch.Interface that watches the requested fentryPrograms.
+func (c *fentryPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
+ var timeout time.Duration
+ if opts.TimeoutSeconds != nil {
+ timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+ }
+ opts.Watch = true
+ return c.client.Get().
+ Resource("fentryprograms").
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Timeout(timeout).
+ Watch(ctx)
+}
+
+// Create takes the representation of a fentryProgram and creates it. Returns the server's representation of the fentryProgram, and an error, if there is any.
+func (c *fentryPrograms) Create(ctx context.Context, fentryProgram *v1alpha1.FentryProgram, opts v1.CreateOptions) (result *v1alpha1.FentryProgram, err error) {
+ result = &v1alpha1.FentryProgram{}
+ err = c.client.Post().
+ Resource("fentryprograms").
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Body(fentryProgram).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// Update takes the representation of a fentryProgram and updates it. Returns the server's representation of the fentryProgram, and an error, if there is any.
+func (c *fentryPrograms) Update(ctx context.Context, fentryProgram *v1alpha1.FentryProgram, opts v1.UpdateOptions) (result *v1alpha1.FentryProgram, err error) {
+ result = &v1alpha1.FentryProgram{}
+ err = c.client.Put().
+ Resource("fentryprograms").
+ Name(fentryProgram.Name).
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Body(fentryProgram).
+ 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 *fentryPrograms) UpdateStatus(ctx context.Context, fentryProgram *v1alpha1.FentryProgram, opts v1.UpdateOptions) (result *v1alpha1.FentryProgram, err error) {
+ result = &v1alpha1.FentryProgram{}
+ err = c.client.Put().
+ Resource("fentryprograms").
+ Name(fentryProgram.Name).
+ SubResource("status").
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Body(fentryProgram).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// Delete takes name of the fentryProgram and deletes it. Returns an error if one occurs.
+func (c *fentryPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
+ return c.client.Delete().
+ Resource("fentryprograms").
+ Name(name).
+ Body(&opts).
+ Do(ctx).
+ Error()
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *fentryPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
+ var timeout time.Duration
+ if listOpts.TimeoutSeconds != nil {
+ timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
+ }
+ return c.client.Delete().
+ Resource("fentryprograms").
+ VersionedParams(&listOpts, scheme.ParameterCodec).
+ Timeout(timeout).
+ Body(&opts).
+ Do(ctx).
+ Error()
+}
+
+// Patch applies the patch and returns the patched fentryProgram.
+func (c *fentryPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.FentryProgram, err error) {
+ result = &v1alpha1.FentryProgram{}
+ err = c.client.Patch(pt).
+ Resource("fentryprograms").
+ Name(name).
+ SubResource(subresources...).
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Body(data).
+ Do(ctx).
+ Into(result)
+ return
+}
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fexitprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fexitprogram.go
new file mode 100644
index 000000000..f9bbcad63
--- /dev/null
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fexitprogram.go
@@ -0,0 +1,184 @@
+/*
+Copyright 2023 The bpfman Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ "context"
+ "time"
+
+ v1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1"
+ scheme "github.com/bpfman/bpfman/bpfman-operator/pkg/client/clientset/versioned/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"
+)
+
+// FexitProgramsGetter has a method to return a FexitProgramInterface.
+// A group's client should implement this interface.
+type FexitProgramsGetter interface {
+ FexitPrograms() FexitProgramInterface
+}
+
+// FexitProgramInterface has methods to work with FexitProgram resources.
+type FexitProgramInterface interface {
+ Create(ctx context.Context, fexitProgram *v1alpha1.FexitProgram, opts v1.CreateOptions) (*v1alpha1.FexitProgram, error)
+ Update(ctx context.Context, fexitProgram *v1alpha1.FexitProgram, opts v1.UpdateOptions) (*v1alpha1.FexitProgram, error)
+ UpdateStatus(ctx context.Context, fexitProgram *v1alpha1.FexitProgram, opts v1.UpdateOptions) (*v1alpha1.FexitProgram, 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.FexitProgram, error)
+ List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.FexitProgramList, 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.FexitProgram, err error)
+ FexitProgramExpansion
+}
+
+// fexitPrograms implements FexitProgramInterface
+type fexitPrograms struct {
+ client rest.Interface
+}
+
+// newFexitPrograms returns a FexitPrograms
+func newFexitPrograms(c *BpfmanV1alpha1Client) *fexitPrograms {
+ return &fexitPrograms{
+ client: c.RESTClient(),
+ }
+}
+
+// Get takes name of the fexitProgram, and returns the corresponding fexitProgram object, and an error if there is any.
+func (c *fexitPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.FexitProgram, err error) {
+ result = &v1alpha1.FexitProgram{}
+ err = c.client.Get().
+ Resource("fexitprograms").
+ Name(name).
+ VersionedParams(&options, scheme.ParameterCodec).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// List takes label and field selectors, and returns the list of FexitPrograms that match those selectors.
+func (c *fexitPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.FexitProgramList, err error) {
+ var timeout time.Duration
+ if opts.TimeoutSeconds != nil {
+ timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+ }
+ result = &v1alpha1.FexitProgramList{}
+ err = c.client.Get().
+ Resource("fexitprograms").
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Timeout(timeout).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// Watch returns a watch.Interface that watches the requested fexitPrograms.
+func (c *fexitPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
+ var timeout time.Duration
+ if opts.TimeoutSeconds != nil {
+ timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+ }
+ opts.Watch = true
+ return c.client.Get().
+ Resource("fexitprograms").
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Timeout(timeout).
+ Watch(ctx)
+}
+
+// Create takes the representation of a fexitProgram and creates it. Returns the server's representation of the fexitProgram, and an error, if there is any.
+func (c *fexitPrograms) Create(ctx context.Context, fexitProgram *v1alpha1.FexitProgram, opts v1.CreateOptions) (result *v1alpha1.FexitProgram, err error) {
+ result = &v1alpha1.FexitProgram{}
+ err = c.client.Post().
+ Resource("fexitprograms").
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Body(fexitProgram).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// Update takes the representation of a fexitProgram and updates it. Returns the server's representation of the fexitProgram, and an error, if there is any.
+func (c *fexitPrograms) Update(ctx context.Context, fexitProgram *v1alpha1.FexitProgram, opts v1.UpdateOptions) (result *v1alpha1.FexitProgram, err error) {
+ result = &v1alpha1.FexitProgram{}
+ err = c.client.Put().
+ Resource("fexitprograms").
+ Name(fexitProgram.Name).
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Body(fexitProgram).
+ 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 *fexitPrograms) UpdateStatus(ctx context.Context, fexitProgram *v1alpha1.FexitProgram, opts v1.UpdateOptions) (result *v1alpha1.FexitProgram, err error) {
+ result = &v1alpha1.FexitProgram{}
+ err = c.client.Put().
+ Resource("fexitprograms").
+ Name(fexitProgram.Name).
+ SubResource("status").
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Body(fexitProgram).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// Delete takes name of the fexitProgram and deletes it. Returns an error if one occurs.
+func (c *fexitPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
+ return c.client.Delete().
+ Resource("fexitprograms").
+ Name(name).
+ Body(&opts).
+ Do(ctx).
+ Error()
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *fexitPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
+ var timeout time.Duration
+ if listOpts.TimeoutSeconds != nil {
+ timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
+ }
+ return c.client.Delete().
+ Resource("fexitprograms").
+ VersionedParams(&listOpts, scheme.ParameterCodec).
+ Timeout(timeout).
+ Body(&opts).
+ Do(ctx).
+ Error()
+}
+
+// Patch applies the patch and returns the patched fexitProgram.
+func (c *fexitPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.FexitProgram, err error) {
+ result = &v1alpha1.FexitProgram{}
+ err = c.client.Patch(pt).
+ Resource("fexitprograms").
+ Name(name).
+ SubResource(subresources...).
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Body(data).
+ Do(ctx).
+ Into(result)
+ return
+}
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/generated_expansion.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/generated_expansion.go
index 95f0e6483..ff5a8ed4d 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/generated_expansion.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/generated_expansion.go
@@ -13,12 +13,17 @@ 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
type BpfProgramExpansion interface{}
+type FentryProgramExpansion interface{}
+
+type FexitProgramExpansion interface{}
+
type KprobeProgramExpansion interface{}
type TcProgramExpansion interface{}
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/kprobeprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/kprobeprogram.go
index 5fbdcfe59..0054fa652 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/kprobeprogram.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/kprobeprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/tcprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/tcprogram.go
index 5baa971a4..b27a6e988 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/tcprogram.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/tcprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/tracepointprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/tracepointprogram.go
index e65fe8997..fe92947dc 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/tracepointprogram.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/tracepointprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/uprobeprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/uprobeprogram.go
index 4752bc6fe..b7350003a 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/uprobeprogram.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/uprobeprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/xdpprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/xdpprogram.go
index 7032681cd..4304fef8f 100644
--- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/xdpprogram.go
+++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/xdpprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/interface.go b/bpfman-operator/pkg/client/informers/externalversions/apis/interface.go
index 60f381797..398b5c1e5 100644
--- a/bpfman-operator/pkg/client/informers/externalversions/apis/interface.go
+++ b/bpfman-operator/pkg/client/informers/externalversions/apis/interface.go
@@ -13,6 +13,7 @@ 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 apis
diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/bpfprogram.go b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/bpfprogram.go
index ef19de4d2..73cc6435e 100644
--- a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/bpfprogram.go
+++ b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/bpfprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/fentryprogram.go b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/fentryprogram.go
new file mode 100644
index 000000000..0cfeab682
--- /dev/null
+++ b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/fentryprogram.go
@@ -0,0 +1,89 @@
+/*
+Copyright 2023 The bpfman Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by informer-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ "context"
+ time "time"
+
+ apisv1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1"
+ versioned "github.com/bpfman/bpfman/bpfman-operator/pkg/client/clientset/versioned"
+ internalinterfaces "github.com/bpfman/bpfman/bpfman-operator/pkg/client/informers/externalversions/internalinterfaces"
+ v1alpha1 "github.com/bpfman/bpfman/bpfman-operator/pkg/client/listers/apis/v1alpha1"
+ 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"
+)
+
+// FentryProgramInformer provides access to a shared informer and lister for
+// FentryPrograms.
+type FentryProgramInformer interface {
+ Informer() cache.SharedIndexInformer
+ Lister() v1alpha1.FentryProgramLister
+}
+
+type fentryProgramInformer struct {
+ factory internalinterfaces.SharedInformerFactory
+ tweakListOptions internalinterfaces.TweakListOptionsFunc
+}
+
+// NewFentryProgramInformer constructs a new informer for FentryProgram 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 NewFentryProgramInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
+ return NewFilteredFentryProgramInformer(client, resyncPeriod, indexers, nil)
+}
+
+// NewFilteredFentryProgramInformer constructs a new informer for FentryProgram 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 NewFilteredFentryProgramInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
+ return cache.NewSharedIndexInformer(
+ &cache.ListWatch{
+ ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.BpfmanV1alpha1().FentryPrograms().List(context.TODO(), options)
+ },
+ WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.BpfmanV1alpha1().FentryPrograms().Watch(context.TODO(), options)
+ },
+ },
+ &apisv1alpha1.FentryProgram{},
+ resyncPeriod,
+ indexers,
+ )
+}
+
+func (f *fentryProgramInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
+ return NewFilteredFentryProgramInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
+}
+
+func (f *fentryProgramInformer) Informer() cache.SharedIndexInformer {
+ return f.factory.InformerFor(&apisv1alpha1.FentryProgram{}, f.defaultInformer)
+}
+
+func (f *fentryProgramInformer) Lister() v1alpha1.FentryProgramLister {
+ return v1alpha1.NewFentryProgramLister(f.Informer().GetIndexer())
+}
diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/fexitprogram.go b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/fexitprogram.go
new file mode 100644
index 000000000..03af8df1e
--- /dev/null
+++ b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/fexitprogram.go
@@ -0,0 +1,89 @@
+/*
+Copyright 2023 The bpfman Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by informer-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ "context"
+ time "time"
+
+ apisv1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1"
+ versioned "github.com/bpfman/bpfman/bpfman-operator/pkg/client/clientset/versioned"
+ internalinterfaces "github.com/bpfman/bpfman/bpfman-operator/pkg/client/informers/externalversions/internalinterfaces"
+ v1alpha1 "github.com/bpfman/bpfman/bpfman-operator/pkg/client/listers/apis/v1alpha1"
+ 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"
+)
+
+// FexitProgramInformer provides access to a shared informer and lister for
+// FexitPrograms.
+type FexitProgramInformer interface {
+ Informer() cache.SharedIndexInformer
+ Lister() v1alpha1.FexitProgramLister
+}
+
+type fexitProgramInformer struct {
+ factory internalinterfaces.SharedInformerFactory
+ tweakListOptions internalinterfaces.TweakListOptionsFunc
+}
+
+// NewFexitProgramInformer constructs a new informer for FexitProgram 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 NewFexitProgramInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
+ return NewFilteredFexitProgramInformer(client, resyncPeriod, indexers, nil)
+}
+
+// NewFilteredFexitProgramInformer constructs a new informer for FexitProgram 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 NewFilteredFexitProgramInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
+ return cache.NewSharedIndexInformer(
+ &cache.ListWatch{
+ ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.BpfmanV1alpha1().FexitPrograms().List(context.TODO(), options)
+ },
+ WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.BpfmanV1alpha1().FexitPrograms().Watch(context.TODO(), options)
+ },
+ },
+ &apisv1alpha1.FexitProgram{},
+ resyncPeriod,
+ indexers,
+ )
+}
+
+func (f *fexitProgramInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
+ return NewFilteredFexitProgramInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
+}
+
+func (f *fexitProgramInformer) Informer() cache.SharedIndexInformer {
+ return f.factory.InformerFor(&apisv1alpha1.FexitProgram{}, f.defaultInformer)
+}
+
+func (f *fexitProgramInformer) Lister() v1alpha1.FexitProgramLister {
+ return v1alpha1.NewFexitProgramLister(f.Informer().GetIndexer())
+}
diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/interface.go b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/interface.go
index 2a020b4aa..b68bddfdb 100644
--- a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/interface.go
+++ b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/interface.go
@@ -13,6 +13,7 @@ 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
@@ -25,6 +26,10 @@ import (
type Interface interface {
// BpfPrograms returns a BpfProgramInformer.
BpfPrograms() BpfProgramInformer
+ // FentryPrograms returns a FentryProgramInformer.
+ FentryPrograms() FentryProgramInformer
+ // FexitPrograms returns a FexitProgramInformer.
+ FexitPrograms() FexitProgramInformer
// KprobePrograms returns a KprobeProgramInformer.
KprobePrograms() KprobeProgramInformer
// TcPrograms returns a TcProgramInformer.
@@ -53,6 +58,16 @@ func (v *version) BpfPrograms() BpfProgramInformer {
return &bpfProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
}
+// FentryPrograms returns a FentryProgramInformer.
+func (v *version) FentryPrograms() FentryProgramInformer {
+ return &fentryProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
+}
+
+// FexitPrograms returns a FexitProgramInformer.
+func (v *version) FexitPrograms() FexitProgramInformer {
+ return &fexitProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
+}
+
// KprobePrograms returns a KprobeProgramInformer.
func (v *version) KprobePrograms() KprobeProgramInformer {
return &kprobeProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/kprobeprogram.go b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/kprobeprogram.go
index f371908b9..56b6b6188 100644
--- a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/kprobeprogram.go
+++ b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/kprobeprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/tcprogram.go b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/tcprogram.go
index 9dd1907ed..61e56aa2b 100644
--- a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/tcprogram.go
+++ b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/tcprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/tracepointprogram.go b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/tracepointprogram.go
index 6c3e4cb95..f60a34312 100644
--- a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/tracepointprogram.go
+++ b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/tracepointprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/uprobeprogram.go b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/uprobeprogram.go
index 430f2a6fa..f36c25d6a 100644
--- a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/uprobeprogram.go
+++ b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/uprobeprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/xdpprogram.go b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/xdpprogram.go
index f3bc9e8a2..36ea795b9 100644
--- a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/xdpprogram.go
+++ b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/xdpprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/informers/externalversions/factory.go b/bpfman-operator/pkg/client/informers/externalversions/factory.go
index 04965035d..aa63614c3 100644
--- a/bpfman-operator/pkg/client/informers/externalversions/factory.go
+++ b/bpfman-operator/pkg/client/informers/externalversions/factory.go
@@ -13,6 +13,7 @@ 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 externalversions
@@ -46,11 +47,6 @@ type sharedInformerFactory struct {
// startedInformers is used for tracking which informers have been started.
// This allows Start() to be called multiple times safely.
startedInformers map[reflect.Type]bool
- // wg tracks how many goroutines were started.
- wg sync.WaitGroup
- // shuttingDown is true when Shutdown has been called. It may still be running
- // because it needs to wait for goroutines.
- shuttingDown bool
}
// WithCustomResyncConfig sets a custom resync period for the specified informer types.
@@ -111,39 +107,20 @@ func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResy
return factory
}
+// Start initializes all requested informers.
func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) {
f.lock.Lock()
defer f.lock.Unlock()
- if f.shuttingDown {
- return
- }
-
for informerType, informer := range f.informers {
if !f.startedInformers[informerType] {
- f.wg.Add(1)
- // We need a new variable in each loop iteration,
- // otherwise the goroutine would use the loop variable
- // and that keeps changing.
- informer := informer
- go func() {
- defer f.wg.Done()
- informer.Run(stopCh)
- }()
+ go informer.Run(stopCh)
f.startedInformers[informerType] = true
}
}
}
-func (f *sharedInformerFactory) Shutdown() {
- f.lock.Lock()
- f.shuttingDown = true
- f.lock.Unlock()
-
- // Will return immediately if there is nothing to wait for.
- f.wg.Wait()
-}
-
+// WaitForCacheSync waits for all started informers' cache were synced.
func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool {
informers := func() map[reflect.Type]cache.SharedIndexInformer {
f.lock.Lock()
@@ -190,57 +167,10 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal
// SharedInformerFactory provides shared informers for resources in all known
// API group versions.
-//
-// It is typically used like this:
-//
-// ctx, cancel := context.Background()
-// defer cancel()
-// factory := NewSharedInformerFactory(client, resyncPeriod)
-// defer factory.WaitForStop() // Returns immediately if nothing was started.
-// genericInformer := factory.ForResource(resource)
-// typedInformer := factory.SomeAPIGroup().V1().SomeType()
-// factory.Start(ctx.Done()) // Start processing these informers.
-// synced := factory.WaitForCacheSync(ctx.Done())
-// for v, ok := range synced {
-// if !ok {
-// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v)
-// return
-// }
-// }
-//
-// // Creating informers can also be created after Start, but then
-// // Start must be called again:
-// anotherGenericInformer := factory.ForResource(resource)
-// factory.Start(ctx.Done())
type SharedInformerFactory interface {
internalinterfaces.SharedInformerFactory
-
- // Start initializes all requested informers. They are handled in goroutines
- // which run until the stop channel gets closed.
- Start(stopCh <-chan struct{})
-
- // Shutdown marks a factory as shutting down. At that point no new
- // informers can be started anymore and Start will return without
- // doing anything.
- //
- // In addition, Shutdown blocks until all goroutines have terminated. For that
- // to happen, the close channel(s) that they were started with must be closed,
- // either before Shutdown gets called or while it is waiting.
- //
- // Shutdown may be called multiple times, even concurrently. All such calls will
- // block until all goroutines have terminated.
- Shutdown()
-
- // WaitForCacheSync blocks until all started informers' caches were synced
- // or the stop channel gets closed.
- WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool
-
- // ForResource gives generic access to a shared informer of the matching type.
ForResource(resource schema.GroupVersionResource) (GenericInformer, error)
-
- // InternalInformerFor returns the SharedIndexInformer for obj using an internal
- // client.
- InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer
+ WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool
Bpfman() apis.Interface
}
diff --git a/bpfman-operator/pkg/client/informers/externalversions/generic.go b/bpfman-operator/pkg/client/informers/externalversions/generic.go
index 4bcf6d181..5514655e8 100644
--- a/bpfman-operator/pkg/client/informers/externalversions/generic.go
+++ b/bpfman-operator/pkg/client/informers/externalversions/generic.go
@@ -13,6 +13,7 @@ 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 externalversions
@@ -54,6 +55,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
// Group=bpfman.io, Version=v1alpha1
case v1alpha1.SchemeGroupVersion.WithResource("bpfprograms"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().BpfPrograms().Informer()}, nil
+ case v1alpha1.SchemeGroupVersion.WithResource("fentryprograms"):
+ return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().FentryPrograms().Informer()}, nil
+ case v1alpha1.SchemeGroupVersion.WithResource("fexitprograms"):
+ 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("tcprograms"):
diff --git a/bpfman-operator/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go b/bpfman-operator/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go
index a5db4b06c..56f5b91d6 100644
--- a/bpfman-operator/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go
+++ b/bpfman-operator/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go
@@ -13,6 +13,7 @@ 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 internalinterfaces
diff --git a/bpfman-operator/pkg/client/listers/apis/v1alpha1/bpfprogram.go b/bpfman-operator/pkg/client/listers/apis/v1alpha1/bpfprogram.go
index 0a613e747..877627d99 100644
--- a/bpfman-operator/pkg/client/listers/apis/v1alpha1/bpfprogram.go
+++ b/bpfman-operator/pkg/client/listers/apis/v1alpha1/bpfprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/listers/apis/v1alpha1/expansion_generated.go b/bpfman-operator/pkg/client/listers/apis/v1alpha1/expansion_generated.go
index 68073a95d..5ee0564f6 100644
--- a/bpfman-operator/pkg/client/listers/apis/v1alpha1/expansion_generated.go
+++ b/bpfman-operator/pkg/client/listers/apis/v1alpha1/expansion_generated.go
@@ -13,6 +13,7 @@ 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
@@ -21,6 +22,14 @@ package v1alpha1
// BpfProgramLister.
type BpfProgramListerExpansion interface{}
+// FentryProgramListerExpansion allows custom methods to be added to
+// FentryProgramLister.
+type FentryProgramListerExpansion interface{}
+
+// FexitProgramListerExpansion allows custom methods to be added to
+// FexitProgramLister.
+type FexitProgramListerExpansion interface{}
+
// KprobeProgramListerExpansion allows custom methods to be added to
// KprobeProgramLister.
type KprobeProgramListerExpansion interface{}
diff --git a/bpfman-operator/pkg/client/listers/apis/v1alpha1/fentryprogram.go b/bpfman-operator/pkg/client/listers/apis/v1alpha1/fentryprogram.go
new file mode 100644
index 000000000..dc505be7c
--- /dev/null
+++ b/bpfman-operator/pkg/client/listers/apis/v1alpha1/fentryprogram.go
@@ -0,0 +1,68 @@
+/*
+Copyright 2023 The bpfman Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by lister-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ v1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/labels"
+ "k8s.io/client-go/tools/cache"
+)
+
+// FentryProgramLister helps list FentryPrograms.
+// All objects returned here must be treated as read-only.
+type FentryProgramLister interface {
+ // List lists all FentryPrograms in the indexer.
+ // Objects returned here must be treated as read-only.
+ List(selector labels.Selector) (ret []*v1alpha1.FentryProgram, err error)
+ // Get retrieves the FentryProgram from the index for a given name.
+ // Objects returned here must be treated as read-only.
+ Get(name string) (*v1alpha1.FentryProgram, error)
+ FentryProgramListerExpansion
+}
+
+// fentryProgramLister implements the FentryProgramLister interface.
+type fentryProgramLister struct {
+ indexer cache.Indexer
+}
+
+// NewFentryProgramLister returns a new FentryProgramLister.
+func NewFentryProgramLister(indexer cache.Indexer) FentryProgramLister {
+ return &fentryProgramLister{indexer: indexer}
+}
+
+// List lists all FentryPrograms in the indexer.
+func (s *fentryProgramLister) List(selector labels.Selector) (ret []*v1alpha1.FentryProgram, err error) {
+ err = cache.ListAll(s.indexer, selector, func(m interface{}) {
+ ret = append(ret, m.(*v1alpha1.FentryProgram))
+ })
+ return ret, err
+}
+
+// Get retrieves the FentryProgram from the index for a given name.
+func (s *fentryProgramLister) Get(name string) (*v1alpha1.FentryProgram, error) {
+ obj, exists, err := s.indexer.GetByKey(name)
+ if err != nil {
+ return nil, err
+ }
+ if !exists {
+ return nil, errors.NewNotFound(v1alpha1.Resource("fentryprogram"), name)
+ }
+ return obj.(*v1alpha1.FentryProgram), nil
+}
diff --git a/bpfman-operator/pkg/client/listers/apis/v1alpha1/fexitprogram.go b/bpfman-operator/pkg/client/listers/apis/v1alpha1/fexitprogram.go
new file mode 100644
index 000000000..a773e71ba
--- /dev/null
+++ b/bpfman-operator/pkg/client/listers/apis/v1alpha1/fexitprogram.go
@@ -0,0 +1,68 @@
+/*
+Copyright 2023 The bpfman Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by lister-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ v1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/labels"
+ "k8s.io/client-go/tools/cache"
+)
+
+// FexitProgramLister helps list FexitPrograms.
+// All objects returned here must be treated as read-only.
+type FexitProgramLister interface {
+ // List lists all FexitPrograms in the indexer.
+ // Objects returned here must be treated as read-only.
+ List(selector labels.Selector) (ret []*v1alpha1.FexitProgram, err error)
+ // Get retrieves the FexitProgram from the index for a given name.
+ // Objects returned here must be treated as read-only.
+ Get(name string) (*v1alpha1.FexitProgram, error)
+ FexitProgramListerExpansion
+}
+
+// fexitProgramLister implements the FexitProgramLister interface.
+type fexitProgramLister struct {
+ indexer cache.Indexer
+}
+
+// NewFexitProgramLister returns a new FexitProgramLister.
+func NewFexitProgramLister(indexer cache.Indexer) FexitProgramLister {
+ return &fexitProgramLister{indexer: indexer}
+}
+
+// List lists all FexitPrograms in the indexer.
+func (s *fexitProgramLister) List(selector labels.Selector) (ret []*v1alpha1.FexitProgram, err error) {
+ err = cache.ListAll(s.indexer, selector, func(m interface{}) {
+ ret = append(ret, m.(*v1alpha1.FexitProgram))
+ })
+ return ret, err
+}
+
+// Get retrieves the FexitProgram from the index for a given name.
+func (s *fexitProgramLister) Get(name string) (*v1alpha1.FexitProgram, error) {
+ obj, exists, err := s.indexer.GetByKey(name)
+ if err != nil {
+ return nil, err
+ }
+ if !exists {
+ return nil, errors.NewNotFound(v1alpha1.Resource("fexitprogram"), name)
+ }
+ return obj.(*v1alpha1.FexitProgram), nil
+}
diff --git a/bpfman-operator/pkg/client/listers/apis/v1alpha1/kprobeprogram.go b/bpfman-operator/pkg/client/listers/apis/v1alpha1/kprobeprogram.go
index 65efa0070..5ae1d443f 100644
--- a/bpfman-operator/pkg/client/listers/apis/v1alpha1/kprobeprogram.go
+++ b/bpfman-operator/pkg/client/listers/apis/v1alpha1/kprobeprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/listers/apis/v1alpha1/tcprogram.go b/bpfman-operator/pkg/client/listers/apis/v1alpha1/tcprogram.go
index 905c3c279..b6ec8190e 100644
--- a/bpfman-operator/pkg/client/listers/apis/v1alpha1/tcprogram.go
+++ b/bpfman-operator/pkg/client/listers/apis/v1alpha1/tcprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/listers/apis/v1alpha1/tracepointprogram.go b/bpfman-operator/pkg/client/listers/apis/v1alpha1/tracepointprogram.go
index dc7ec6107..224a175d4 100644
--- a/bpfman-operator/pkg/client/listers/apis/v1alpha1/tracepointprogram.go
+++ b/bpfman-operator/pkg/client/listers/apis/v1alpha1/tracepointprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/listers/apis/v1alpha1/uprobeprogram.go b/bpfman-operator/pkg/client/listers/apis/v1alpha1/uprobeprogram.go
index 78af46298..6f0eb828b 100644
--- a/bpfman-operator/pkg/client/listers/apis/v1alpha1/uprobeprogram.go
+++ b/bpfman-operator/pkg/client/listers/apis/v1alpha1/uprobeprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/client/listers/apis/v1alpha1/xdpprogram.go b/bpfman-operator/pkg/client/listers/apis/v1alpha1/xdpprogram.go
index d485a39aa..995985eb7 100644
--- a/bpfman-operator/pkg/client/listers/apis/v1alpha1/xdpprogram.go
+++ b/bpfman-operator/pkg/client/listers/apis/v1alpha1/xdpprogram.go
@@ -13,6 +13,7 @@ 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
diff --git a/bpfman-operator/pkg/helpers/helpers.go b/bpfman-operator/pkg/helpers/helpers.go
index c58be99f5..7a51e979b 100644
--- a/bpfman-operator/pkg/helpers/helpers.go
+++ b/bpfman-operator/pkg/helpers/helpers.go
@@ -46,6 +46,7 @@ const (
Tc ProgramType = 3
Tracepoint ProgramType = 5
Xdp ProgramType = 6
+ Tracing ProgramType = 26
)
func (p ProgramType) Uint32() *uint32 {
@@ -64,6 +65,8 @@ func FromString(p string) (*ProgramType, error) {
programType = Xdp
case "tracepoint":
programType = Tracepoint
+ case "tracing":
+ programType = Tracing
default:
return nil, fmt.Errorf("unknown program type: %s", p)
}
@@ -81,6 +84,8 @@ func (p ProgramType) String() string {
return "xdp"
case Tracepoint:
return "tracepoint"
+ case Tracing:
+ return "tracing"
default:
return ""
}
@@ -163,7 +168,28 @@ func isKprobebpfmanProgLoaded(c *bpfmanclientset.Clientset, progConfName string)
progLoaded, condType := isProgLoaded(&bpfProgConfig.Status.Conditions)
if !progLoaded {
- log.Info("kprobProgram: %s not ready with condition: %s, waiting until timeout", progConfName, condType)
+ log.Info("kprobeProgram: %s not ready with condition: %s, waiting until timeout", progConfName, condType)
+ return false, nil
+ }
+
+ return true, nil
+ }
+}
+
+func isFentrybpfmanProgLoaded(c *bpfmanclientset.Clientset, progConfName string) wait.ConditionFunc {
+ ctx := context.Background()
+
+ return func() (bool, error) {
+ log.Info(".") // progress bar!
+ bpfProgConfig, err := c.BpfmanV1alpha1().FentryPrograms().Get(ctx, progConfName, metav1.GetOptions{})
+ if err != nil {
+ return false, err
+ }
+
+ progLoaded, condType := isProgLoaded(&bpfProgConfig.Status.Conditions)
+
+ if !progLoaded {
+ log.Info("fentryProgram: %s not ready with condition: %s, waiting until timeout", progConfName, condType)
return false, nil
}
@@ -246,10 +272,16 @@ func WaitForBpfProgConfLoad(c *bpfmanclientset.Clientset, progName string, timeo
return wait.PollImmediate(time.Second, timeout, isXdpbpfmanProgLoaded(c, progName))
case Tracepoint:
return wait.PollImmediate(time.Second, timeout, isTracepointbpfmanProgLoaded(c, progName))
+ case Tracing:
+ return wait.PollImmediate(time.Second, timeout, isFentrybpfmanProgLoaded(c, progName))
// TODO: case Uprobe: not covered. Since Uprobe has the same ProgramType as
// Kprobe, we need a different way to distinguish them. Options include
// creating an internal ProgramType for Uprobe or using a different
// identifier such as the string representation of the program type.
+ // TODO: case Fexit: not covered. Since Fexit has the same ProgramType as
+ // Fentry, we need a different way to distinguish them. Options include
+ // creating an internal ProgramType for Fexit or using a different
+ // identifier such as the string representation of the program type.
default:
return fmt.Errorf("unknown bpf program type: %s", progType)
}
diff --git a/bpfman-operator/test/integration/kprobe_test.go b/bpfman-operator/test/integration/kprobe_test.go
new file mode 100644
index 000000000..f80ab2e76
--- /dev/null
+++ b/bpfman-operator/test/integration/kprobe_test.go
@@ -0,0 +1,70 @@
+//go:build integration_tests
+// +build integration_tests
+
+package integration
+
+import (
+ "bytes"
+ "context"
+ "io"
+ "regexp"
+ "strconv"
+ "testing"
+ "time"
+
+ "github.com/kong/kubernetes-testing-framework/pkg/clusters"
+ "github.com/stretchr/testify/require"
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+const (
+ kprobeGoCounterKustomize = "../../../examples/config/default/go-kprobe-counter"
+ kprobeGoCounterUserspaceNs = "go-kprobe-counter"
+ kprobeGoCounterUserspaceDsName = "go-kprobe-counter-ds"
+)
+
+func TestKprobeGoCounter(t *testing.T) {
+ t.Log("deploying kprobe counter program")
+ require.NoError(t, clusters.KustomizeDeployForCluster(ctx, env.Cluster(), kprobeGoCounterKustomize))
+ addCleanup(func(context.Context) error {
+ cleanupLog("cleaning up kprobe counter program")
+ return clusters.KustomizeDeleteForCluster(ctx, env.Cluster(), kprobeGoCounterKustomize)
+ })
+
+ t.Log("waiting for go kprobe counter userspace daemon to be available")
+ require.Eventually(t, func() bool {
+ daemon, err := env.Cluster().Client().AppsV1().DaemonSets(kprobeGoCounterUserspaceNs).Get(ctx, kprobeGoCounterUserspaceDsName, metav1.GetOptions{})
+ require.NoError(t, err)
+ return daemon.Status.DesiredNumberScheduled == daemon.Status.NumberAvailable
+ },
+ // Wait 5 minutes since cosign is slow, https://github.com/bpfman/bpfman/issues/1043
+ 5*time.Minute, 10*time.Second)
+
+ pods, err := env.Cluster().Client().CoreV1().Pods(kprobeGoCounterUserspaceNs).List(ctx, metav1.ListOptions{LabelSelector: "name=go-kprobe-counter"})
+ require.NoError(t, err)
+ goKprobeCounterPod := pods.Items[0]
+
+ want := regexp.MustCompile(`Kprobe count: ([0-9]+)`)
+ req := env.Cluster().Client().CoreV1().Pods(kprobeGoCounterUserspaceNs).GetLogs(goKprobeCounterPod.Name, &corev1.PodLogOptions{})
+ require.Eventually(t, func() bool {
+ logs, err := req.Stream(ctx)
+ require.NoError(t, err)
+ defer logs.Close()
+ output := new(bytes.Buffer)
+ _, err = io.Copy(output, logs)
+ require.NoError(t, err)
+ t.Logf("counter pod log %s", output.String())
+
+ matches := want.FindAllStringSubmatch(output.String(), -1)
+ if len(matches) >= 1 && len(matches[0]) >= 2 {
+ count, err := strconv.Atoi(matches[0][1])
+ require.NoError(t, err)
+ if count > 0 {
+ t.Logf("counted %d kprobe executions so far, BPF program is functioning", count)
+ return true
+ }
+ }
+ return false
+ }, 30*time.Second, time.Second)
+}
diff --git a/bpfman-operator/test/integration/tc_test.go b/bpfman-operator/test/integration/tc_test.go
index 00258662e..386e31d61 100644
--- a/bpfman-operator/test/integration/tc_test.go
+++ b/bpfman-operator/test/integration/tc_test.go
@@ -36,7 +36,9 @@ func TestTcGoCounter(t *testing.T) {
daemon, err := env.Cluster().Client().AppsV1().DaemonSets(tcGoCounterUserspaceNs).Get(ctx, tcGoCounterUserspaceDsName, metav1.GetOptions{})
require.NoError(t, err)
return daemon.Status.DesiredNumberScheduled == daemon.Status.NumberAvailable
- }, time.Minute, time.Second)
+ },
+ // Wait 5 minutes since cosign is slow, https://github.com/bpfman/bpfman/issues/1043
+ 5 * time.Minute, 10 * time.Second)
pods, err := env.Cluster().Client().CoreV1().Pods(tcGoCounterUserspaceNs).List(ctx, metav1.ListOptions{LabelSelector: "name=go-tc-counter"})
require.NoError(t, err)
diff --git a/bpfman-operator/test/integration/tracepoint_test.go b/bpfman-operator/test/integration/tracepoint_test.go
index d1c3a57ce..1a4d9207f 100644
--- a/bpfman-operator/test/integration/tracepoint_test.go
+++ b/bpfman-operator/test/integration/tracepoint_test.go
@@ -37,8 +37,10 @@ func TestTracepointGoCounter(t *testing.T) {
daemon, err := env.Cluster().Client().AppsV1().DaemonSets(tracepointGoCounterUserspaceNs).Get(ctx, tracepointGoCounterUserspaceDsName, metav1.GetOptions{})
require.NoError(t, err)
return daemon.Status.DesiredNumberScheduled == daemon.Status.NumberAvailable
- }, time.Minute, time.Second)
-
+ },
+ // Wait 5 minutes since cosign is slow, https://github.com/bpfman/bpfman/issues/1043
+ 5 * time.Minute, 10 * time.Second)
+
pods, err := env.Cluster().Client().CoreV1().Pods(tracepointGoCounterUserspaceNs).List(ctx, metav1.ListOptions{LabelSelector: "name=go-tracepoint-counter"})
require.NoError(t, err)
goTracepointCounterPod := pods.Items[0]
diff --git a/bpfman-operator/test/integration/uprobe_test.go b/bpfman-operator/test/integration/uprobe_test.go
new file mode 100644
index 000000000..4be4ae443
--- /dev/null
+++ b/bpfman-operator/test/integration/uprobe_test.go
@@ -0,0 +1,89 @@
+//go:build integration_tests
+// +build integration_tests
+
+package integration
+
+import (
+ "bytes"
+ "context"
+ "io"
+ "regexp"
+ "strconv"
+ "testing"
+ "time"
+
+ "github.com/kong/kubernetes-testing-framework/pkg/clusters"
+ "github.com/stretchr/testify/require"
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+const (
+ uprobeGoCounterKustomize = "../../../examples/config/default/go-uprobe-counter"
+ uprobeGoCounterUserspaceNs = "go-uprobe-counter"
+ uprobeGoCounterUserspaceDsName = "go-uprobe-counter-ds"
+ targetKustomize = "../../../examples/config/default/go-target"
+ targetUserspaceNs = "go-target"
+ targetUserspaceDsName = "go-target-ds"
+)
+
+func TestUprobeGoCounter(t *testing.T) {
+ t.Log("deploying target for uprobe counter program")
+ require.NoError(t, clusters.KustomizeDeployForCluster(ctx, env.Cluster(), targetKustomize))
+ addCleanup(func(context.Context) error {
+ cleanupLog("cleaning up target program")
+ return clusters.KustomizeDeleteForCluster(ctx, env.Cluster(), targetKustomize)
+ })
+
+ t.Log("waiting for go target userspace daemon to be available")
+ require.Eventually(t, func() bool {
+ daemon, err := env.Cluster().Client().AppsV1().DaemonSets(targetUserspaceNs).Get(ctx, targetUserspaceDsName, metav1.GetOptions{})
+ require.NoError(t, err)
+ return daemon.Status.DesiredNumberScheduled == daemon.Status.NumberAvailable
+ },
+ // Wait 5 minutes since cosign is slow, https://github.com/bpfman/bpfman/issues/1043
+ 5*time.Minute, 10*time.Second)
+
+ t.Log("deploying uprobe counter program")
+ require.NoError(t, clusters.KustomizeDeployForCluster(ctx, env.Cluster(), uprobeGoCounterKustomize))
+ addCleanup(func(context.Context) error {
+ cleanupLog("cleaning up uprobe counter program")
+ return clusters.KustomizeDeleteForCluster(ctx, env.Cluster(), uprobeGoCounterKustomize)
+ })
+
+ t.Log("waiting for go uprobe counter userspace daemon to be available")
+ require.Eventually(t, func() bool {
+ daemon, err := env.Cluster().Client().AppsV1().DaemonSets(uprobeGoCounterUserspaceNs).Get(ctx, uprobeGoCounterUserspaceDsName, metav1.GetOptions{})
+ require.NoError(t, err)
+ return daemon.Status.DesiredNumberScheduled == daemon.Status.NumberAvailable
+ },
+ // Wait 5 minutes since cosign is slow, https://github.com/bpfman/bpfman/issues/1043
+ 5*time.Minute, 10*time.Second)
+
+ pods, err := env.Cluster().Client().CoreV1().Pods(uprobeGoCounterUserspaceNs).List(ctx, metav1.ListOptions{LabelSelector: "name=go-uprobe-counter"})
+ require.NoError(t, err)
+ goUprobeCounterPod := pods.Items[0]
+
+ want := regexp.MustCompile(`Uprobe count: ([0-9]+)`)
+ req := env.Cluster().Client().CoreV1().Pods(uprobeGoCounterUserspaceNs).GetLogs(goUprobeCounterPod.Name, &corev1.PodLogOptions{})
+ require.Eventually(t, func() bool {
+ logs, err := req.Stream(ctx)
+ require.NoError(t, err)
+ defer logs.Close()
+ output := new(bytes.Buffer)
+ _, err = io.Copy(output, logs)
+ require.NoError(t, err)
+ t.Logf("counter pod log %s", output.String())
+
+ matches := want.FindAllStringSubmatch(output.String(), -1)
+ if len(matches) >= 1 && len(matches[0]) >= 2 {
+ count, err := strconv.Atoi(matches[0][1])
+ require.NoError(t, err)
+ if count > 0 {
+ t.Logf("counted %d uprobe executions so far, BPF program is functioning", count)
+ return true
+ }
+ }
+ return false
+ }, 30*time.Second, time.Second)
+}
diff --git a/bpfman-operator/test/integration/xdp_test.go b/bpfman-operator/test/integration/xdp_test.go
index ccb1ff6da..3f92dcb52 100644
--- a/bpfman-operator/test/integration/xdp_test.go
+++ b/bpfman-operator/test/integration/xdp_test.go
@@ -6,7 +6,9 @@ package integration
import (
"bytes"
"context"
+ "encoding/base64"
"io"
+ "os"
"strings"
"testing"
"time"
@@ -16,6 +18,7 @@ import (
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "sigs.k8s.io/yaml"
)
const (
@@ -24,28 +27,43 @@ const (
xdpGoCounterUserspaceDsName = "go-xdp-counter-ds"
)
+var xdpPassPrivateImageCreds = os.Getenv("XDP_PASS_PRIVATE_IMAGE_CREDS")
+
func TestXdpPassPrivate(t *testing.T) {
t.Log("deploying secret for privated xdp bytecode image in the bpfman namespace")
// Generated from
/*
- kubectl create secret -n bpfman docker-registry regcred --docker-server=quay.io --docker-username=bpfman-bytecode+bpfmancreds --docker-password=D49CKWI1MMOFGRCAT8SHW5A56FSVP30TGYX54BBWKY2J129XRI6Q5TVH2ZZGTJ1M
+ kubectl create secret -n bpfman docker-registry regcred --docker-server=quay.io --docker-username= --docker-password=
*/
- xdpPassPrivateSecretYAML := `---
----
-apiVersion: v1
-kind: Secret
-metadata:
- name: regcred
- namespace: default
-type: kubernetes.io/dockerconfigjson
-data:
- .dockerconfigjson: eyJhdXRocyI6eyJxdWF5LmlvIjp7InVzZXJuYW1lIjoiYnBmbWFuLWJ5dGVjb2RlK2JwZm1hbmNyZWRzIiwicGFzc3dvcmQiOiJENDlDS1dJMU1NT0ZHUkNBVDhTSFc1QTU2RlNWUDMwVEdZWDU0QkJXS1kySjEyOVhSSTZRNVRWSDJaWkdUSjFNIiwiYXV0aCI6IlluQm1iV0Z1TFdKNWRHVmpiMlJsSzJKd1ptMWhibU55WldSek9rUTBPVU5MVjBreFRVMVBSa2RTUTBGVU9GTklWelZCTlRaR1UxWlFNekJVUjFsWU5UUkNRbGRMV1RKS01USTVXRkpKTmxFMVZGWklNbHBhUjFSS01VMD0ifX19
-`
- require.NoError(t, clusters.ApplyManifestByYAML(ctx, env.Cluster(), xdpPassPrivateSecretYAML))
+ if xdpPassPrivateImageCreds == "" {
+ t.Log("XDP_PASS_PRIVATE_IMAGE_CREDS must be provided to run this test, skipping")
+ t.SkipNow()
+ }
+
+ xdpPassPrivateJson, err := base64.StdEncoding.DecodeString(xdpPassPrivateImageCreds)
+ require.NoError(t, err)
+
+ xdpPassPrivateSecret := &corev1.Secret{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "Secret",
+ APIVersion: "v1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "regcred",
+ Namespace: "default",
+ },
+ Type: corev1.SecretTypeDockerConfigJson,
+ Data: map[string][]byte{".dockerconfigjson": []byte(xdpPassPrivateJson)},
+ }
+
+ yamlData, err := yaml.Marshal(xdpPassPrivateSecret)
+ require.NoError(t, err)
+
+ require.NoError(t, clusters.ApplyManifestByYAML(ctx, env.Cluster(), string(yamlData)))
addCleanup(func(ctx context.Context) error {
cleanupLog("cleaning up xdp pass private secret")
- return clusters.DeleteManifestByYAML(ctx, env.Cluster(), xdpPassPrivateSecretYAML)
+ return clusters.DeleteManifestByYAML(ctx, env.Cluster(), string(yamlData))
})
xdpPassPrivateXdpProgramYAML := `---
@@ -98,7 +116,9 @@ func TestXdpGoCounter(t *testing.T) {
daemon, err := env.Cluster().Client().AppsV1().DaemonSets(xdpGoCounterUserspaceNs).Get(ctx, xdpGoCounterUserspaceDsName, metav1.GetOptions{})
require.NoError(t, err)
return daemon.Status.DesiredNumberScheduled == daemon.Status.NumberAvailable
- }, time.Minute, time.Second)
+ },
+ // Wait 5 minutes since cosign is slow, https://github.com/bpfman/bpfman/issues/1043
+ 5 * time.Minute, 10 * time.Second)
pods, err := env.Cluster().Client().CoreV1().Pods(xdpGoCounterUserspaceNs).List(ctx, metav1.ListOptions{LabelSelector: "name=go-xdp-counter"})
require.NoError(t, err)
diff --git a/bpfman.spec b/bpfman.spec
new file mode 100644
index 000000000..72157fffd
--- /dev/null
+++ b/bpfman.spec
@@ -0,0 +1,107 @@
+# Generated by rust2rpm 25
+%bcond_without check
+
+%global crate bpfman
+%global commit GITSHA
+%global shortcommit GITSHORTSHA
+%global base_version 0.4.0
+%global prerelease dev
+%global package_version %{base_version}%{?prerelease:~%{prerelease}}
+%global upstream_version %{base_version}%{?prerelease:~%{prerelease}}
+
+Name: bpfman
+Version: %{package_version}
+Release: %autorelease
+Summary: An eBPF program manager
+
+SourceLicense: Apache-2.0
+# (Apache-2.0 OR MIT) AND BSD-3-Clause
+# (MIT OR Apache-2.0) AND Unicode-DFS-2016
+# 0BSD OR MIT OR Apache-2.0
+# Apache-2.0
+# Apache-2.0 OR BSL-1.0
+# Apache-2.0 OR ISC OR MIT
+# Apache-2.0 OR MIT
+# Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT
+# BSD-2-Clause OR Apache-2.0 OR MIT
+# BSD-3-Clause
+# ISC
+# MIT
+# MIT AND BSD-3-Clause
+# MIT OR Apache-2.0
+# MIT OR Apache-2.0 OR BSD-1-Clause
+# MIT OR Apache-2.0 OR Zlib
+# MIT OR Zlib OR Apache-2.0
+# MPL-2.0
+# Unlicense OR MIT
+# Zlib OR Apache-2.0 OR MIT
+License: Apache-2.0 AND Unicode-DFS-2016 AND BSD-3-Clause AND ISC AND MIT AND MPL-2.0
+# LICENSE.dependencies contains a full license breakdown
+
+URL: https://bpfman.io
+Source0: https://github.com/bpfman/bpfman/archive/%{commit}/%{name}-%{shortcommit}.tar.gz
+# The vendor tarball is created using cargo-vendor-filterer to remove Windows
+# related files (https://github.com/coreos/cargo-vendor-filterer)
+# cargo vendor-filterer --format tar.gz --prefix vendor bpfman-bpfman-vendor.tar.gz
+Source1: bpfman-bpfman-vendor.tar.gz
+
+BuildRequires: cargo-rpm-macros >= 25
+BuildRequires: systemd-rpm-macros
+BuildRequires: openssl-devel
+
+# TODO: Generate Provides for all of the vendored dependencies
+
+%global _description %{expand:
+An eBPF Program Manager.}
+
+%description %{_description}
+
+%prep
+%autosetup %{name}-%{version_no_tilde} -n %{name}-%{commit} -p1 -a1
+
+# Source1 is vendored dependencies
+tar -xoaf %{SOURCE1}
+# Let the macros setup Cargo.toml to use vendored sources
+%cargo_prep -v vendor
+%cargo_license_summary
+%cargo_license
+
+%build
+%cargo_build
+%{cargo_license_summary}
+%{cargo_license} > LICENSE.dependencies
+%{cargo_vendor_manifest}
+
+%install
+install -Dpm 0755 \
+ -t %{buildroot}%{_sbindir} \
+ ./target/release/bpfman
+install -Dpm 644 \
+ -t %{buildroot}%{_unitdir} \
+ ./scripts/bpfman.socket
+install -Dpm 644 \
+ -t %{buildroot}%{_unitdir} \
+ ./scripts/bpfman.service
+
+%post
+%systemd_post bpfman.socket
+
+%preun
+%systemd_preun bpfman.socket
+
+%postun
+%systemd_postun_with_restart bpfman.socket
+
+%files
+%license LICENSE-APACHE
+%license LICENSE-BSD2
+%license LICENSE-GPL2
+%license LICENSE.dependencies
+%license cargo-vendor.txt
+%doc README.md
+%{_sbindir}/bpfman
+%{_unitdir}/bpfman.socket
+%{_unitdir}/bpfman.service
+
+%changelog
+%autochangelog
diff --git a/bpfman/Cargo.toml b/bpfman/Cargo.toml
index b0851e3c9..59b5f0ac4 100644
--- a/bpfman/Cargo.toml
+++ b/bpfman/Cargo.toml
@@ -1,14 +1,18 @@
[package]
-description = "A system daemon for loading BPF programs"
-edition = "2021"
-license = "Apache-2.0"
+description = "An eBPF Program Manager"
+edition.workspace = true
+license.workspace = true
name = "bpfman"
-repository = "https://github.com/bpfman/bpfman"
-version = "0.3.1"
+repository.workspace = true
+version.workspace = true
+
+[lib]
+name = "bpfman"
+path = "src/lib.rs"
[[bin]]
name = "bpfman"
-path = "src/main.rs"
+path = "src/bin/cli/main.rs"
[dependencies]
anyhow = { workspace = true, features = ["std"] }
@@ -16,7 +20,6 @@ async-trait = { workspace = true }
aya = { workspace = true }
base16ct = { workspace = true, features = ["alloc"] }
base64 = { workspace = true }
-bpfman-api = { workspace = true }
bpfman-csi = { workspace = true }
caps = { workspace = true }
chrono = { workspace = true }
@@ -33,6 +36,7 @@ env_logger = { workspace = true }
flate2 = { workspace = true, features = ["zlib"] }
futures = { workspace = true }
hex = { workspace = true, features = ["std"] }
+lazy_static = { workspace = true }
log = { workspace = true }
netlink-packet-route = { workspace = true }
nix = { workspace = true, features = [
@@ -44,29 +48,30 @@ nix = { workspace = true, features = [
"user",
] }
oci-distribution = { workspace = true, default-features = false, features = [
- "rustls-tls",
+ "native-tls",
"trust-dns",
] }
+rand = { workspace = true }
rtnetlink = { workspace = true, features = ["tokio_socket"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["std"] }
sha2 = { workspace = true }
sigstore = { workspace = true, features = [
"cached-client",
- "cosign-rustls-tls",
- "tuf",
+ "cosign-native-tls",
+ "sigstore-trust-root",
] }
+sled = { workspace = true }
systemd-journal-logger = { workspace = true }
tar = { workspace = true }
tempfile = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["full", "signal"] }
tokio-stream = { workspace = true, features = ["net"] }
-toml = { workspace = true }
+toml = { workspace = true, features = ["parse"] }
tonic = { workspace = true, features = ["transport"] }
tower = { workspace = true }
url = { workspace = true }
-users = { workspace = true }
[dev-dependencies]
assert_matches = { workspace = true }
diff --git a/bpfman/src/bin/cli/args.rs b/bpfman/src/bin/cli/args.rs
new file mode 100644
index 000000000..0f0f7448b
--- /dev/null
+++ b/bpfman/src/bin/cli/args.rs
@@ -0,0 +1,358 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Authors of bpfman
+
+use bpfman::types::ProgramType;
+use clap::{Args, Parser, Subcommand};
+use hex::FromHex;
+
+#[derive(Parser, Debug)]
+#[command(
+ long_about = "An eBPF manager focusing on simplifying the deployment and administration of eBPF programs."
+)]
+#[command(name = "bpfman")]
+#[command(disable_version_flag = true)]
+pub(crate) struct Cli {
+ #[command(subcommand)]
+ pub(crate) command: Commands,
+}
+
+#[derive(Subcommand, Debug)]
+pub(crate) enum Commands {
+ /// Load an eBPF program on the system.
+ #[command(subcommand)]
+ Load(LoadSubcommand),
+ /// Unload an eBPF program using the Program Id.
+ Unload(UnloadArgs),
+ /// List all eBPF programs loaded via bpfman.
+ List(ListArgs),
+ /// Get an eBPF program using the Program Id.
+ Get(GetArgs),
+ /// eBPF Bytecode Image related commands.
+ #[command(subcommand)]
+ Image(ImageSubCommand),
+}
+
+#[derive(Subcommand, Debug)]
+#[command(disable_version_flag = true)]
+pub(crate) enum LoadSubcommand {
+ /// Load an eBPF program from a local .o file.
+ File(LoadFileArgs),
+ /// Load an eBPF program packaged in a OCI container image from a given registry.
+ Image(LoadImageArgs),
+}
+
+#[derive(Args, Debug)]
+pub(crate) struct LoadFileArgs {
+ /// Required: Location of local bytecode file
+ /// Example: --path /run/bpfman/examples/go-xdp-counter/bpf_bpfel.o
+ #[clap(short, long, verbatim_doc_comment)]
+ pub(crate) path: String,
+
+ /// Required: The name of the function that is the entry point for the BPF program.
+ #[clap(short, long)]
+ pub(crate) name: String,
+
+ /// Optional: Global variables to be set when program is loaded.
+ /// Format: =
+ ///
+ /// 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.
+ #[clap(short, long, verbatim_doc_comment, num_args(1..), value_parser=parse_global_arg)]
+ pub(crate) global: Option>,
+
+ /// Optional: Specify Key/Value metadata to be attached to a program when it
+ /// is loaded by bpfman.
+ /// Format: =
+ ///
+ /// This can later be used to `list` a certain subset of programs which contain
+ /// the specified metadata.
+ /// Example: --metadata owner=acme
+ #[clap(short, long, verbatim_doc_comment, value_parser=parse_key_val, value_delimiter = ',')]
+ pub(crate) metadata: Option>,
+
+ /// Optional: Program Id of loaded eBPF program this eBPF program will share a map with.
+ /// Only used when multiple eBPF programs need to share a map.
+ /// Example: --map-owner-id 63178
+ #[clap(long, verbatim_doc_comment)]
+ pub(crate) map_owner_id: Option,
+
+ #[clap(subcommand)]
+ pub(crate) command: LoadCommands,
+}
+
+#[derive(Args, Debug)]
+pub(crate) struct LoadImageArgs {
+ /// Specify how the bytecode image should be pulled.
+ #[command(flatten)]
+ pub(crate) pull_args: PullBytecodeArgs,
+
+ /// Optional: The name of the function that is the entry point for the BPF program.
+ /// If not provided, the program name defined as part of the bytecode image will be used.
+ #[clap(short, long, verbatim_doc_comment, default_value = "")]
+ pub(crate) name: String,
+
+ /// Optional: Global variables to be set when program is loaded.
+ /// Format: =
+ ///
+ /// 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.
+ #[clap(short, long, verbatim_doc_comment, num_args(1..), value_parser=parse_global_arg)]
+ pub(crate) global: Option>,
+
+ /// Optional: Specify Key/Value metadata to be attached to a program when it
+ /// is loaded by bpfman.
+ /// Format: =
+ ///
+ /// This can later be used to list a certain subset of programs which contain
+ /// the specified metadata.
+ /// Example: --metadata owner=acme
+ #[clap(short, long, verbatim_doc_comment, value_parser=parse_key_val, value_delimiter = ',')]
+ pub(crate) metadata: Option>,
+
+ /// Optional: Program Id of loaded eBPF program this eBPF program will share a map with.
+ /// Only used when multiple eBPF programs need to share a map.
+ /// Example: --map-owner-id 63178
+ #[clap(long, verbatim_doc_comment)]
+ pub(crate) map_owner_id: Option,
+
+ #[clap(subcommand)]
+ pub(crate) command: LoadCommands,
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct GlobalArg {
+ pub(crate) name: String,
+ pub(crate) value: Vec,
+}
+
+#[derive(Subcommand, Debug)]
+#[command(disable_version_flag = true)]
+pub(crate) enum LoadCommands {
+ #[command(disable_version_flag = true)]
+ /// Install an eBPF program on the XDP hook point for a given interface.
+ Xdp {
+ /// Required: Interface to load program on.
+ #[clap(short, long)]
+ iface: String,
+
+ /// Required: Priority to run program in chain. Lower value runs first.
+ #[clap(short, long)]
+ priority: i32,
+
+ /// Optional: Proceed to call other programs in chain on this exit code.
+ /// Multiple values supported by repeating the parameter.
+ /// Example: --proceed-on "pass" --proceed-on "drop"
+ ///
+ /// [possible values: aborted, drop, pass, tx, redirect, dispatcher_return]
+ ///
+ /// [default: pass, dispatcher_return]
+ #[clap(long, verbatim_doc_comment, num_args(1..))]
+ proceed_on: Vec,
+ },
+ #[command(disable_version_flag = true)]
+ /// Install an eBPF program on the TC hook point for a given interface.
+ Tc {
+ /// Required: Direction to apply program.
+ ///
+ /// [possible values: ingress, egress]
+ #[clap(short, long, verbatim_doc_comment)]
+ direction: String,
+
+ /// Required: Interface to load program on.
+ #[clap(short, long)]
+ iface: String,
+
+ /// Required: Priority to run program in chain. Lower value runs first.
+ #[clap(short, long)]
+ priority: i32,
+
+ /// Optional: Proceed to call other programs in chain on this exit code.
+ /// Multiple values supported by repeating the parameter.
+ /// Example: --proceed-on "ok" --proceed-on "pipe"
+ ///
+ /// [possible values: unspec, ok, reclassify, shot, pipe, stolen, queued,
+ /// repeat, redirect, trap, dispatcher_return]
+ ///
+ /// [default: ok, pipe, dispatcher_return]
+ #[clap(long, verbatim_doc_comment, num_args(1..))]
+ proceed_on: Vec,
+ },
+ #[command(disable_version_flag = true)]
+ /// Install an eBPF program on a Tracepoint.
+ Tracepoint {
+ /// Required: The tracepoint to attach to.
+ /// Example: --tracepoint "sched/sched_switch"
+ #[clap(short, long, verbatim_doc_comment)]
+ tracepoint: String,
+ },
+ #[command(disable_version_flag = true)]
+ /// Install a kprobe or kretprobe eBPF probe
+ Kprobe {
+ /// Required: Function to attach the kprobe to.
+ #[clap(short, long)]
+ fn_name: String,
+
+ /// Optional: Offset added to the address of the function for kprobe.
+ /// Not allowed for kretprobes.
+ #[clap(short, long, verbatim_doc_comment)]
+ offset: Option,
+
+ /// Optional: Whether the program is a kretprobe.
+ ///
+ /// [default: false]
+ #[clap(short, long, verbatim_doc_comment)]
+ retprobe: bool,
+
+ /// Optional: Host PID of container to attach the kprobe in.
+ /// (NOT CURRENTLY SUPPORTED)
+ #[clap(short, long)]
+ container_pid: Option,
+ },
+ #[command(disable_version_flag = true)]
+ /// Install a uprobe or uretprobe eBPF probe
+ Uprobe {
+ /// Optional: Function to attach the uprobe to.
+ #[clap(short, long)]
+ fn_name: Option,
+
+ /// Optional: Offset added to the address of the target function (or
+ /// beginning of target if no function is identified). Offsets are
+ /// supported for uretprobes, but use with caution because they can
+ /// result in unintended side effects.
+ #[clap(short, long, verbatim_doc_comment)]
+ offset: Option,
+
+ /// Required: Library name or the absolute path to a binary or library.
+ /// Example: --target "libc".
+ #[clap(short, long, verbatim_doc_comment)]
+ target: String,
+
+ /// Optional: Whether the program is a uretprobe.
+ ///
+ /// [default: false]
+ #[clap(short, long, verbatim_doc_comment)]
+ retprobe: bool,
+
+ /// Optional: Only execute uprobe for given process identification number (PID).
+ /// If PID is not provided, uprobe executes for all PIDs.
+ #[clap(short, long, verbatim_doc_comment)]
+ pid: Option,
+
+ /// Optional: Host PID of container to attach the uprobe in.
+ /// (NOT CURRENTLY SUPPORTED)
+ #[clap(short, long)]
+ container_pid: Option,
+ },
+ #[command(disable_version_flag = true)]
+ /// Install a fentry eBPF probe
+ Fentry {
+ /// Required: Kernel function to attach the fentry probe.
+ #[clap(short, long)]
+ fn_name: String,
+ },
+ #[command(disable_version_flag = true)]
+ /// Install a fexit eBPF probe
+ Fexit {
+ /// Required: Kernel function to attach the fexit probe.
+ #[clap(short, long)]
+ fn_name: String,
+ },
+}
+
+#[derive(Args, Debug)]
+#[command(disable_version_flag = true)]
+pub(crate) struct UnloadArgs {
+ /// Required: Program Id to be unloaded.
+ pub(crate) program_id: u32,
+}
+
+#[derive(Args, Debug)]
+#[command(disable_version_flag = true)]
+pub(crate) struct ListArgs {
+ /// Optional: List a specific program type
+ /// Example: --program-type xdp
+ ///
+ /// [possible values: unspec, socket-filter, probe, tc, sched-act,
+ /// tracepoint, xdp, perf-event, cgroup-skb,
+ /// cgroup-sock, lwt-in, lwt-out, lwt-xmit, sock-ops,
+ /// sk-skb, cgroup-device, sk-msg, raw-tracepoint,
+ /// cgroup-sock-addr, lwt-seg6-local, lirc-mode2,
+ /// sk-reuseport, flow-dissector, cgroup-sysctl,
+ /// raw-tracepoint-writable, cgroup-sockopt, tracing,
+ /// struct-ops, ext, lsm, sk-lookup, syscall]
+ #[clap(short, long, verbatim_doc_comment, hide_possible_values = true)]
+ pub(crate) program_type: Option,
+
+ /// Optional: List programs which contain a specific set of metadata labels
+ /// that were applied when the program was loaded with `--metadata` parameter.
+ /// Format: =
+ ///
+ /// Example: --metadata-selector owner=acme
+ #[clap(short, long, verbatim_doc_comment, value_parser=parse_key_val, value_delimiter = ',')]
+ pub(crate) metadata_selector: Option>,
+
+ /// Optional: List all programs.
+ #[clap(short, long, verbatim_doc_comment)]
+ pub(crate) all: bool,
+}
+
+#[derive(Args, Debug)]
+#[command(disable_version_flag = true)]
+pub(crate) struct GetArgs {
+ /// Required: Program Id to get.
+ pub(crate) program_id: u32,
+}
+
+#[derive(Subcommand, Debug)]
+#[command(disable_version_flag = true)]
+pub(crate) enum ImageSubCommand {
+ /// Pull an eBPF bytecode image from a remote registry.
+ Pull(PullBytecodeArgs),
+}
+
+#[derive(Args, Debug)]
+#[command(disable_version_flag = true)]
+pub(crate) struct PullBytecodeArgs {
+ /// Required: Container Image URL.
+ /// Example: --image-url quay.io/bpfman-bytecode/xdp_pass:latest
+ #[clap(short, long, verbatim_doc_comment)]
+ pub(crate) image_url: String,
+
+ /// Optional: Registry auth for authenticating with the specified image registry.
+ /// This should be base64 encoded from the ':' string just like
+ /// it's stored in the docker/podman host config.
+ /// Example: --registry_auth "YnjrcKw63PhDcQodiU9hYxQ2"
+ #[clap(short, long, verbatim_doc_comment)]
+ pub(crate) registry_auth: Option,
+
+ /// Optional: Pull policy for remote images.
+ ///
+ /// [possible values: Always, IfNotPresent, Never]
+ #[clap(short, long, verbatim_doc_comment, default_value = "IfNotPresent")]
+ pub(crate) pull_policy: String,
+}
+
+/// Parse a single key-value pair
+pub(crate) fn parse_key_val(s: &str) -> Result<(String, String), std::io::Error> {
+ let pos = s.find('=').ok_or(std::io::ErrorKind::InvalidInput)?;
+ Ok((s[..pos].to_string(), s[pos + 1..].to_string()))
+}
+
+pub(crate) fn parse_global_arg(global_arg: &str) -> Result {
+ let mut parts = global_arg.split('=');
+
+ let name_str = parts.next().ok_or(std::io::ErrorKind::InvalidInput)?;
+
+ let value_str = parts.next().ok_or(std::io::ErrorKind::InvalidInput)?;
+ let value = Vec::::from_hex(value_str).map_err(|_e| std::io::ErrorKind::InvalidInput)?;
+ if value.is_empty() {
+ return Err(std::io::ErrorKind::InvalidInput.into());
+ }
+
+ Ok(GlobalArg {
+ name: name_str.to_string(),
+ value,
+ })
+}
diff --git a/bpfman/src/bin/cli/get.rs b/bpfman/src/bin/cli/get.rs
new file mode 100644
index 000000000..91809e80c
--- /dev/null
+++ b/bpfman/src/bin/cli/get.rs
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Authors of bpfman
+
+use bpfman::{errors::BpfmanError, get_program};
+use log::warn;
+
+use crate::{args::GetArgs, table::ProgTable};
+
+pub(crate) async fn execute_get(args: &GetArgs) -> Result<(), BpfmanError> {
+ match get_program(args.program_id).await {
+ Ok(program) => {
+ ProgTable::new_program(&program)?.print();
+ ProgTable::new_kernel_info(&program)?.print();
+ Ok(())
+ }
+ Err(e) => {
+ warn!("BPFMAN get error: {}", e);
+ Err(e)
+ }
+ }
+}
diff --git a/bpfman/src/bin/cli/image.rs b/bpfman/src/bin/cli/image.rs
new file mode 100644
index 000000000..3b2de6cf9
--- /dev/null
+++ b/bpfman/src/bin/cli/image.rs
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Authors of bpfman
+
+use base64::{engine::general_purpose, Engine};
+use bpfman::{
+ pull_bytecode,
+ types::{BytecodeImage, ImagePullPolicy},
+};
+
+use crate::args::{ImageSubCommand, PullBytecodeArgs};
+
+impl ImageSubCommand {
+ pub(crate) async fn execute(&self) -> anyhow::Result<()> {
+ match self {
+ ImageSubCommand::Pull(args) => execute_pull(args).await,
+ }
+ }
+}
+
+impl TryFrom<&PullBytecodeArgs> for BytecodeImage {
+ type Error = anyhow::Error;
+
+ fn try_from(value: &PullBytecodeArgs) -> Result {
+ let image_pull_policy: ImagePullPolicy = value.pull_policy.as_str().try_into()?;
+ let (username, password) = match &value.registry_auth {
+ Some(a) => {
+ let auth_raw = general_purpose::STANDARD.decode(a)?;
+ let auth_string = String::from_utf8(auth_raw)?;
+ let (username, password) = auth_string.split_once(':').unwrap();
+ (Some(username.to_owned()), Some(password.to_owned()))
+ }
+ None => (None, None),
+ };
+
+ Ok(BytecodeImage {
+ image_url: value.image_url.clone(),
+ image_pull_policy,
+ username,
+ password,
+ })
+ }
+}
+
+pub(crate) async fn execute_pull(args: &PullBytecodeArgs) -> anyhow::Result<()> {
+ let image: BytecodeImage = args.try_into()?;
+ pull_bytecode(image).await?;
+
+ Ok(())
+}
diff --git a/bpfman/src/bin/cli/list.rs b/bpfman/src/bin/cli/list.rs
new file mode 100644
index 000000000..76e403ed0
--- /dev/null
+++ b/bpfman/src/bin/cli/list.rs
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Authors of bpfman
+
+use anyhow::bail;
+use bpfman::{list_programs, types::ListFilter};
+
+use crate::{args::ListArgs, table::ProgTable};
+
+pub(crate) async fn execute_list(args: &ListArgs) -> anyhow::Result<()> {
+ let prog_type_filter = args.program_type.map(|p| p as u32);
+
+ let filter = ListFilter::new(
+ prog_type_filter,
+ args.metadata_selector
+ .clone()
+ .unwrap_or_default()
+ .iter()
+ .map(|(k, v)| (k.to_owned(), v.to_owned()))
+ .collect(),
+ !args.all,
+ );
+
+ let mut table = ProgTable::new_list();
+
+ for r in list_programs(filter).await? {
+ if let Err(e) = table.add_response_prog(r) {
+ bail!(e)
+ }
+ }
+ table.print();
+ Ok(())
+}
diff --git a/bpfman/src/bin/cli/load.rs b/bpfman/src/bin/cli/load.rs
new file mode 100644
index 000000000..b40018a6d
--- /dev/null
+++ b/bpfman/src/bin/cli/load.rs
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Authors of bpfman
+
+use std::collections::HashMap;
+
+use anyhow::bail;
+use bpfman::{
+ add_program,
+ types::{
+ FentryProgram, FexitProgram, KprobeProgram, Location, Program, ProgramData, TcProceedOn,
+ TcProgram, TracepointProgram, UprobeProgram, XdpProceedOn, XdpProgram,
+ },
+};
+
+use crate::{
+ args::{GlobalArg, LoadCommands, LoadFileArgs, LoadImageArgs, LoadSubcommand},
+ table::ProgTable,
+};
+
+impl LoadSubcommand {
+ pub(crate) async fn execute(&self) -> anyhow::Result<()> {
+ match self {
+ LoadSubcommand::File(l) => execute_load_file(l).await,
+ LoadSubcommand::Image(l) => execute_load_image(l).await,
+ }
+ }
+}
+
+pub(crate) async fn execute_load_file(args: &LoadFileArgs) -> anyhow::Result<()> {
+ let bytecode_source = Location::File(args.path.clone());
+
+ let data = ProgramData::new(
+ bytecode_source,
+ args.name.clone(),
+ args.metadata
+ .clone()
+ .unwrap_or_default()
+ .iter()
+ .map(|(k, v)| (k.to_owned(), v.to_owned()))
+ .collect(),
+ parse_global(&args.global),
+ args.map_owner_id,
+ )?;
+
+ let program = add_program(args.command.get_program(data)?).await?;
+
+ ProgTable::new_program(&program)?.print();
+ ProgTable::new_kernel_info(&program)?.print();
+ Ok(())
+}
+
+pub(crate) async fn execute_load_image(args: &LoadImageArgs) -> anyhow::Result<()> {
+ let bytecode_source = Location::Image((&args.pull_args).try_into()?);
+
+ let data = ProgramData::new(
+ bytecode_source,
+ args.name.clone(),
+ args.metadata
+ .clone()
+ .unwrap_or_default()
+ .iter()
+ .map(|(k, v)| (k.to_owned(), v.to_owned()))
+ .collect(),
+ parse_global(&args.global),
+ args.map_owner_id,
+ )?;
+
+ let program = add_program(args.command.get_program(data)?).await?;
+
+ ProgTable::new_program(&program)?.print();
+ ProgTable::new_kernel_info(&program)?.print();
+ Ok(())
+}
+
+impl LoadCommands {
+ pub(crate) fn get_program(&self, data: ProgramData) -> Result {
+ match self {
+ LoadCommands::Xdp {
+ iface,
+ priority,
+ proceed_on,
+ } => {
+ let proc_on = match XdpProceedOn::from_strings(proceed_on) {
+ Ok(p) => p,
+ Err(e) => bail!("error parsing proceed_on {e}"),
+ };
+ Ok(Program::Xdp(XdpProgram::new(
+ data,
+ *priority,
+ iface.to_string(),
+ XdpProceedOn::from_int32s(proc_on.as_action_vec())?,
+ )?))
+ }
+ LoadCommands::Tc {
+ direction,
+ iface,
+ priority,
+ proceed_on,
+ } => {
+ match direction.as_str() {
+ "ingress" | "egress" => (),
+ other => bail!("{} is not a valid direction", other),
+ };
+ let proc_on = match TcProceedOn::from_strings(proceed_on) {
+ Ok(p) => p,
+ Err(e) => bail!("error parsing proceed_on {e}"),
+ };
+ Ok(Program::Tc(TcProgram::new(
+ data,
+ *priority,
+ iface.to_string(),
+ proc_on,
+ direction.to_string().try_into()?,
+ )?))
+ }
+ LoadCommands::Tracepoint { tracepoint } => Ok(Program::Tracepoint(
+ TracepointProgram::new(data, tracepoint.to_string())?,
+ )),
+ LoadCommands::Kprobe {
+ fn_name,
+ offset,
+ retprobe,
+ container_pid,
+ } => {
+ if container_pid.is_some() {
+ bail!("kprobe container option not supported yet");
+ }
+ let offset = offset.unwrap_or(0);
+ Ok(Program::Kprobe(KprobeProgram::new(
+ data,
+ fn_name.to_string(),
+ offset,
+ *retprobe,
+ None,
+ )?))
+ }
+ LoadCommands::Uprobe {
+ fn_name,
+ offset,
+ target,
+ retprobe,
+ pid,
+ container_pid,
+ } => {
+ let offset = offset.unwrap_or(0);
+ Ok(Program::Uprobe(UprobeProgram::new(
+ data,
+ fn_name.clone(),
+ offset,
+ target.to_string(),
+ *retprobe,
+ *pid,
+ *container_pid,
+ )?))
+ }
+ LoadCommands::Fentry { fn_name } => Ok(Program::Fentry(FentryProgram::new(
+ data,
+ fn_name.to_string(),
+ )?)),
+ LoadCommands::Fexit { fn_name } => Ok(Program::Fexit(FexitProgram::new(
+ data,
+ fn_name.to_string(),
+ )?)),
+ }
+ }
+}
+
+fn parse_global(global: &Option>) -> HashMap> {
+ let mut global_data: HashMap> = HashMap::new();
+
+ if let Some(global) = global {
+ for g in global.iter() {
+ global_data.insert(g.name.to_string(), g.value.clone());
+ }
+ }
+ global_data
+}
diff --git a/bpfman/src/bin/cli/main.rs b/bpfman/src/bin/cli/main.rs
new file mode 100644
index 000000000..3c27cefb1
--- /dev/null
+++ b/bpfman/src/bin/cli/main.rs
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Authors of bpfman
+
+use anyhow::anyhow;
+use args::Commands;
+use clap::Parser;
+use get::execute_get;
+use list::execute_list;
+use unload::execute_unload;
+
+mod args;
+mod get;
+mod image;
+mod list;
+mod load;
+mod table;
+mod unload;
+
+#[tokio::main]
+async fn main() -> anyhow::Result<()> {
+ let cli = crate::args::Cli::parse();
+
+ cli.command.execute().await
+}
+
+impl Commands {
+ pub(crate) async fn execute(&self) -> Result<(), anyhow::Error> {
+ match self {
+ Commands::Load(l) => l.execute().await,
+ Commands::Unload(args) => execute_unload(args).await,
+ Commands::List(args) => execute_list(args).await,
+ Commands::Get(args) => execute_get(args)
+ .await
+ .map_err(|e| anyhow!("get error: {e}")),
+ Commands::Image(i) => i.execute().await,
+ }?;
+
+ Ok(())
+ }
+}
diff --git a/bpfman/src/bin/cli/table.rs b/bpfman/src/bin/cli/table.rs
new file mode 100644
index 000000000..c3ebda84c
--- /dev/null
+++ b/bpfman/src/bin/cli/table.rs
@@ -0,0 +1,271 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Authors of bpfman
+
+use bpfman::types::{ImagePullPolicy, Location, ProbeType::*, Program};
+use comfy_table::{Cell, Color, Table};
+use hex::encode_upper;
+pub(crate) struct ProgTable(Table);
+
+impl ProgTable {
+ pub(crate) fn new_program(program: &Program) -> Result {
+ let mut table = Table::new();
+
+ table.load_preset(comfy_table::presets::NOTHING);
+ table.set_header(vec![Cell::new("Bpfman State")
+ .add_attribute(comfy_table::Attribute::Bold)
+ .add_attribute(comfy_table::Attribute::Underlined)
+ .fg(Color::Green)]);
+
+ let data = program.get_data();
+
+ let name = data.get_name()?;
+ if name.is_empty() {
+ table.add_row(vec!["Name:", "None"]);
+ } else {
+ table.add_row(vec!["Name:", &name]);
+ }
+
+ match data.get_location()? {
+ Location::Image(i) => {
+ table.add_row(vec!["Image URL:", &i.image_url]);
+ table.add_row(vec![
+ "Pull Policy:",
+ &format! { "{}", TryInto::::try_into(i.image_pull_policy)?},
+ ]);
+ }
+ Location::File(p) => {
+ table.add_row(vec!["Path:", &p]);
+ }
+ };
+
+ let global_data = data.get_global_data()?;
+ if global_data.is_empty() {
+ table.add_row(vec!["Global:", "None"]);
+ } else {
+ let mut first = true;
+ for (key, value) in global_data {
+ let data = &format! {"{key}={}", encode_upper(value)};
+ if first {
+ first = false;
+ table.add_row(vec!["Global:", data]);
+ } else {
+ table.add_row(vec!["", data]);
+ }
+ }
+ }
+
+ let metadata = data.get_metadata()?;
+ if metadata.is_empty() {
+ table.add_row(vec!["Metadata:", "None"]);
+ } else {
+ let mut first = true;
+ for (key, value) in metadata.clone() {
+ let data = &format! {"{key}={value}"};
+ if first {
+ first = false;
+ table.add_row(vec!["Metadata:", data]);
+ } else {
+ table.add_row(vec!["", data]);
+ }
+ }
+ }
+
+ if let Some(map_pin_path) = data.get_map_pin_path()? {
+ table.add_row(vec![
+ "Map Pin Path:",
+ map_pin_path
+ .to_str()
+ .expect("map_pin_path is not valid Unicode"),
+ ]);
+ } else {
+ table.add_row(vec!["Map Pin Path:", "None"]);
+ }
+
+ match data.get_map_owner_id()? {
+ Some(id) => table.add_row(vec!["Map Owner ID:", &id.to_string()]),
+ None => table.add_row(vec!["Map Owner ID:", "None"]),
+ };
+
+ let map_used_by = data.get_maps_used_by()?;
+ if map_used_by.is_empty() {
+ table.add_row(vec!["Maps Used By:", "None"]);
+ } else {
+ let mut first = true;
+ for prog_id in map_used_by {
+ if first {
+ first = false;
+ table.add_row(vec!["Maps Used By:", &prog_id.to_string()]);
+ } else {
+ table.add_row(vec!["", &prog_id.to_string()]);
+ }
+ }
+ };
+
+ match program {
+ Program::Xdp(p) => {
+ table.add_row(vec!["Priority:", &p.get_priority()?.to_string()]);
+ table.add_row(vec!["Iface:", &p.get_iface()?]);
+ table.add_row(vec![
+ "Position:",
+ &match p.get_current_position()? {
+ Some(pos) => pos.to_string(),
+ None => "NONE".to_string(),
+ },
+ ]);
+ table.add_row(vec!["Proceed On:", &format!("{}", p.get_proceed_on()?)]);
+ }
+ Program::Tc(p) => {
+ table.add_row(vec!["Priority:", &p.get_priority()?.to_string()]);
+ table.add_row(vec!["Iface:", &p.get_iface()?]);
+ table.add_row(vec![
+ "Position:",
+ &match p.get_current_position()? {
+ Some(pos) => pos.to_string(),
+ None => "NONE".to_string(),
+ },
+ ]);
+ table.add_row(vec!["Direction:", &p.get_direction()?.to_string()]);
+ table.add_row(vec!["Proceed On:", &format!("{}", p.get_proceed_on()?)]);
+ }
+ Program::Tracepoint(p) => {
+ table.add_row(vec!["Tracepoint:", &p.get_tracepoint()?]);
+ }
+ Program::Kprobe(p) => {
+ let probe_type = match p.get_retprobe()? {
+ true => Kretprobe,
+ false => Kprobe,
+ };
+
+ table.add_row(vec!["Probe Type:", &format!["{probe_type}"]]);
+ table.add_row(vec!["Function Name:", &p.get_fn_name()?]);
+ table.add_row(vec!["Offset:", &p.get_offset()?.to_string()]);
+ table.add_row(vec![
+ "PID:",
+ &p.get_container_pid()?.unwrap_or(0).to_string(),
+ ]);
+ }
+ Program::Uprobe(p) => {
+ let probe_type = match p.get_retprobe()? {
+ true => Kretprobe,
+ false => Kprobe,
+ };
+ table.add_row(vec!["Probe Type:", &format!["{probe_type}"]]);
+ table.add_row(vec![
+ "Function Name:",
+ &p.get_fn_name()?.unwrap_or("NONE".to_string()),
+ ]);
+ table.add_row(vec!["Offset:", &p.get_offset()?.to_string()]);
+ table.add_row(vec!["Target:", &p.get_target()?]);
+ table.add_row(vec!["PID", &p.get_pid()?.unwrap_or(0).to_string()]);
+ table.add_row(vec![
+ "Container PID:",
+ &p.get_container_pid()?.unwrap_or(0).to_string(),
+ ]);
+ }
+ Program::Fentry(p) => {
+ table.add_row(vec!["Function Name:", &p.get_fn_name()?]);
+ }
+ Program::Fexit(p) => {
+ table.add_row(vec!["Function Name:", &p.get_fn_name()?]);
+ }
+ Program::Unsupported(_) => {
+ table.add_row(vec!["Unsupported Program Type", "None"]);
+ }
+ }
+ Ok(ProgTable(table))
+ }
+
+ pub(crate) fn new_kernel_info(r: &Program) -> Result {
+ let mut table = Table::new();
+
+ table.load_preset(comfy_table::presets::NOTHING);
+ table.set_header(vec![Cell::new("Kernel State")
+ .add_attribute(comfy_table::Attribute::Bold)
+ .add_attribute(comfy_table::Attribute::Underlined)
+ .fg(Color::Green)]);
+
+ let p = r.get_data();
+ let name = if p.get_kernel_name()?.is_empty() {
+ "None".to_string()
+ } else {
+ p.get_kernel_name()?
+ };
+
+ let rows = vec![
+ vec!["Program ID:".to_string(), p.get_id()?.to_string()],
+ vec!["Name:".to_string(), name],
+ vec!["Type:".to_string(), format!("{}", r.kind())],
+ vec!["Loaded At:".to_string(), p.get_kernel_loaded_at()?],
+ vec!["Tag:".to_string(), p.get_kernel_tag()?],
+ vec![
+ "GPL Compatible:".to_string(),
+ p.get_kernel_gpl_compatible()?.to_string(),
+ ],
+ vec![
+ "Map IDs:".to_string(),
+ format!("{:?}", p.get_kernel_map_ids()?),
+ ],
+ vec!["BTF ID:".to_string(), p.get_kernel_btf_id()?.to_string()],
+ vec![
+ "Size Translated (bytes):".to_string(),
+ p.get_kernel_bytes_xlated()?.to_string(),
+ ],
+ vec!["JITted:".to_string(), p.get_kernel_jited()?.to_string()],
+ vec![
+ "Size JITted:".to_string(),
+ p.get_kernel_bytes_jited()?.to_string(),
+ ],
+ vec![
+ "Kernel Allocated Memory (bytes):".to_string(),
+ p.get_kernel_bytes_memlock()?.to_string(),
+ ],
+ vec![
+ "Verified Instruction Count:".to_string(),
+ p.get_kernel_verified_insns()?.to_string(),
+ ],
+ ];
+ table.add_rows(rows);
+ Ok(ProgTable(table))
+ }
+
+ pub(crate) fn new_list() -> Self {
+ let mut table = Table::new();
+
+ table.load_preset(comfy_table::presets::NOTHING);
+ table.set_header(vec!["Program ID", "Name", "Type", "Load Time"]);
+ ProgTable(table)
+ }
+
+ pub(crate) fn add_row_list(
+ &mut self,
+ id: String,
+ name: String,
+ type_: String,
+ load_time: String,
+ ) {
+ self.0.add_row(vec![id, name, type_, load_time]);
+ }
+
+ pub(crate) fn add_response_prog(&mut self, r: Program) -> anyhow::Result<()> {
+ let data = r.get_data();
+
+ self.add_row_list(
+ data.get_id()?.to_string(),
+ data.get_kernel_name()?,
+ r.kind().to_string(),
+ data.get_kernel_loaded_at()?,
+ );
+
+ Ok(())
+ }
+
+ pub(crate) fn print(&self) {
+ println!("{self}\n")
+ }
+}
+
+impl std::fmt::Display for ProgTable {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
diff --git a/bpfman/src/bin/cli/unload.rs b/bpfman/src/bin/cli/unload.rs
new file mode 100644
index 000000000..b204e1412
--- /dev/null
+++ b/bpfman/src/bin/cli/unload.rs
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Authors of bpfman
+
+use bpfman::remove_program;
+
+use crate::args::UnloadArgs;
+
+pub(crate) async fn execute_unload(args: &UnloadArgs) -> Result<(), anyhow::Error> {
+ remove_program(args.program_id).await?;
+ Ok(())
+}
diff --git a/bpfman/src/bpf.rs b/bpfman/src/bpf.rs
deleted file mode 100644
index fc29d1f06..000000000
--- a/bpfman/src/bpf.rs
+++ /dev/null
@@ -1,1097 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-// Copyright Authors of bpfman
-
-use std::{
- collections::HashMap,
- convert::TryInto,
- path::{Path, PathBuf},
-};
-
-use anyhow::anyhow;
-use aya::{
- programs::{
- kprobe::KProbeLink, links::FdLink, loaded_programs, trace_point::TracePointLink,
- uprobe::UProbeLink, KProbe, TracePoint, UProbe,
- },
- BpfLoader,
-};
-use bpfman_api::{
- config::Config,
- util::directories::*,
- ProbeType::{self, *},
- ProgramType, TcProceedOn,
-};
-use log::{debug, info, warn};
-use tokio::{
- fs::{create_dir_all, read_dir, remove_dir_all},
- select,
- sync::{
- mpsc::{Receiver, Sender},
- oneshot,
- },
-};
-
-use crate::{
- command::{
- BpfMap, Command, Direction,
- Direction::{Egress, Ingress},
- Program, ProgramData, PullBytecodeArgs, TcProgram, UnloadArgs,
- },
- errors::BpfmanError,
- multiprog::{Dispatcher, DispatcherId, DispatcherInfo, TcDispatcher, XdpDispatcher},
- oci_utils::image_manager::Command as ImageManagerCommand,
- serve::shutdown_handler,
- utils::{get_ifindex, set_dir_permissions, should_map_be_pinned},
-};
-
-const MAPS_MODE: u32 = 0o0660;
-
-pub(crate) struct BpfManager {
- config: Config,
- dispatchers: DispatcherMap,
- programs: ProgramMap,
- maps: HashMap,
- commands: Receiver,
- image_manager: Sender,
-}
-
-pub(crate) struct ProgramMap {
- programs: HashMap,
-}
-
-impl ProgramMap {
- fn new() -> Self {
- ProgramMap {
- programs: HashMap::new(),
- }
- }
-
- fn insert(&mut self, id: u32, prog: Program) -> Option {
- self.programs.insert(id, prog)
- }
-
- fn remove(&mut self, id: &u32) -> Option {
- self.programs.remove(id)
- }
-
- fn get_mut(&mut self, id: &u32) -> Option<&mut Program> {
- self.programs.get_mut(id)
- }
-
- fn get(&self, id: &u32) -> Option<&Program> {
- self.programs.get(id)
- }
-
- fn programs_mut<'a>(
- &'a mut self,
- program_type: &'a ProgramType,
- if_index: &'a Option,
- direction: &'a Option,
- ) -> impl Iterator- {
- self.programs.values_mut().filter(|p| {
- p.kind() == *program_type && p.if_index() == *if_index && p.direction() == *direction
- })
- }
-
- // Sets the positions of programs that are to be attached via a dispatcher.
- // Positions are set based on order of priority. Ties are broken based on:
- // - Already attached programs are preferred
- // - Program name. Lowest lexical order wins.
- fn set_program_positions(&mut self, program: &mut Program, is_add: bool) {
- let program_type = program.kind();
- let if_index = program.if_index();
- let direction = program.direction();
-
- let mut extensions = self
- .programs
- .values_mut()
- .filter(|p| {
- p.kind() == program_type && p.if_index() == if_index && p.direction() == direction
- })
- .collect::>();
-
- if is_add {
- // add program we're loading
- extensions.push(program);
- }
-
- extensions.sort_by_key(|b| {
- (
- b.priority(),
- b.attached().unwrap(),
- b.data()
- .expect("All Bpfman programs should have ProgramData")
- .name()
- .to_owned(),
- )
- });
- for (i, v) in extensions.iter_mut().enumerate() {
- v.set_position(Some(i));
- }
- }
-
- fn get_programs_iter(&self) -> impl Iterator
- {
- self.programs.values().map(|p| {
- let kernel_info = p
- .data()
- .expect("All Bpfman programs should have ProgramData")
- .kernel_info()
- .expect("Loaded Bpfman programs should have kernel information");
-
- (kernel_info.id, p)
- })
- }
-}
-
-pub(crate) struct DispatcherMap {
- dispatchers: HashMap,
-}
-
-impl DispatcherMap {
- fn new() -> Self {
- DispatcherMap {
- dispatchers: HashMap::new(),
- }
- }
-
- fn remove(&mut self, id: &DispatcherId) -> Option {
- self.dispatchers.remove(id)
- }
-
- fn insert(&mut self, id: DispatcherId, dis: Dispatcher) -> Option {
- self.dispatchers.insert(id, dis)
- }
-
- /// Returns the number of extension programs currently attached to the dispatcher that
- /// would be used to attach the provided [`Program`].
- fn attached_programs(&self, did: &DispatcherId) -> usize {
- if let Some(d) = self.dispatchers.get(did) {
- d.num_extensions()
- } else {
- 0
- }
- }
-}
-
-impl BpfManager {
- pub(crate) fn new(
- config: Config,
- commands: Receiver,
- image_manager: Sender,
- ) -> Self {
- Self {
- config,
- dispatchers: DispatcherMap::new(),
- programs: ProgramMap::new(),
- maps: HashMap::new(),
- commands,
- image_manager,
- }
- }
-
- pub(crate) async fn rebuild_state(&mut self) -> Result<(), anyhow::Error> {
- debug!("BpfManager::rebuild_state()");
- let mut programs_dir = read_dir(RTDIR_PROGRAMS).await?;
- while let Some(entry) = programs_dir.next_entry().await? {
- let id = entry.file_name().to_string_lossy().parse().unwrap();
- let mut program = Program::load(id)
- .map_err(|e| BpfmanError::Error(format!("cant read program state {e}")))?;
- // TODO: Should probably check for pinned prog on bpffs rather than assuming they are attached
- program.set_attached();
- debug!("rebuilding state for program {}", id);
- self.rebuild_map_entry(id, &mut program).await;
- self.programs.insert(id, program);
- }
- self.rebuild_dispatcher_state(ProgramType::Xdp, None, RTDIR_XDP_DISPATCHER)
- .await?;
- self.rebuild_dispatcher_state(ProgramType::Tc, Some(Ingress), RTDIR_TC_INGRESS_DISPATCHER)
- .await?;
- self.rebuild_dispatcher_state(ProgramType::Tc, Some(Egress), RTDIR_TC_EGRESS_DISPATCHER)
- .await?;
-
- Ok(())
- }
-
- pub(crate) async fn rebuild_dispatcher_state(
- &mut self,
- program_type: ProgramType,
- direction: Option,
- path: &str,
- ) -> Result<(), anyhow::Error> {
- let mut dispatcher_dir = read_dir(path).await?;
- while let Some(entry) = dispatcher_dir.next_entry().await? {
- let name = entry.file_name();
- let parts: Vec<&str> = name.to_str().unwrap().split('_').collect();
- if parts.len() != 2 {
- continue;
- }
- let if_index: u32 = parts[0].parse().unwrap();
- let revision: u32 = parts[1].parse().unwrap();
- match program_type {
- ProgramType::Xdp => {
- let dispatcher = XdpDispatcher::load(if_index, revision).unwrap();
- self.dispatchers.insert(
- DispatcherId::Xdp(DispatcherInfo(if_index, None)),
- Dispatcher::Xdp(dispatcher),
- );
- }
- ProgramType::Tc => {
- let direction = direction.expect("direction required for tc programs");
-
- let dispatcher = TcDispatcher::load(if_index, direction, revision).unwrap();
- let did = DispatcherId::Tc(DispatcherInfo(if_index, Some(direction)));
-
- self.dispatchers.insert(
- DispatcherId::Tc(DispatcherInfo(if_index, Some(direction))),
- Dispatcher::Tc(dispatcher),
- );
-
- // This is just used to collect and sort the dispatcher's
- // programs in `rebuild_multiattach_dispatcher`.
- // TODO(astoycos) rebuilding dispacthers need to be actual helpers
- // on BpfManager.Dispatchers not BpfManager itself.
- let fake_prog_filter = Program::Tc(TcProgram {
- data: ProgramData::default(),
- priority: 0,
- if_index: Some(if_index),
- iface: String::new(),
- proceed_on: TcProceedOn::default(),
- direction,
- current_position: None,
- attached: false,
- });
-
- self.rebuild_multiattach_dispatcher(fake_prog_filter, did)
- .await?;
- }
- _ => return Err(anyhow!("invalid program type {:?}", program_type)),
- }
- }
-
- Ok(())
- }
-
- pub(crate) async fn add_program(
- &mut self,
- mut program: Program,
- ) -> Result {
- let map_owner_id = program.data()?.map_owner_id();
- // Set map_pin_path if we're using another program's maps
- if let Some(map_owner_id) = map_owner_id {
- let map_pin_path = self.is_map_owner_id_valid(map_owner_id)?;
- program
- .data_mut()?
- .set_map_pin_path(Some(map_pin_path.clone()));
- }
-
- program
- .data_mut()?
- .set_program_bytes(self.image_manager.clone())
- .await?;
-
- let result = match program {
- Program::Xdp(_) | Program::Tc(_) => {
- program.set_if_index(get_ifindex(&program.if_name().unwrap())?);
-
- self.add_multi_attach_program(&mut program).await
- }
- Program::Tracepoint(_) | Program::Kprobe(_) | Program::Uprobe(_) => {
- self.add_single_attach_program(&mut program).await
- }
- Program::Unsupported(_) => panic!("Cannot add unsupported program"),
- };
-
- // Program bytes MUST be cleared after load.
- program.data_mut()?.clear_program_bytes();
-
- match result {
- Ok(id) => {
- info!(
- "Added {} program with name: {} and id: {id}",
- program.kind(),
- program.data()?.name()
- );
-
- // Now that program is successfully loaded, update the id, maps hash table,
- // and allow access to all maps by bpfman group members.
- self.save_map(&mut program, id, map_owner_id).await?;
-
- // Only add program to bpfManager if we've completed all mutations and it's successfully loaded.
- self.programs.insert(id, program.to_owned());
-
- Ok(program)
- }
- Err(e) => {
- // Cleanup any directories associated with the map_pin_path.
- // Data and map_pin_path may or may not exist depending on where the original
- // error occured, so don't error if not there and preserve original error.
- if let Ok(data) = program.data() {
- if let Some(pin_path) = data.map_pin_path() {
- let _ = self.cleanup_map_pin_path(pin_path, map_owner_id).await;
- }
- }
- Err(e)
- }
- }
- }
-
- pub(crate) async fn add_multi_attach_program(
- &mut self,
- program: &mut Program,
- ) -> Result {
- debug!("BpfManager::add_multi_attach_program()");
- let name = program.data()?.name();
-
- // This load is just to verify the BPF Function Name is valid.
- // The actual load is performed in the XDP or TC logic.
- // don't pin maps here.
- let mut ext_loader = BpfLoader::new()
- .allow_unsupported_maps()
- .extension(name)
- .load(program.data()?.program_bytes())?;
-
- match ext_loader.program_mut(name) {
- Some(_) => Ok(()),
- None => Err(BpfmanError::BpfFunctionNameNotValid(name.to_owned())),
- }?;
-
- let did = program
- .dispatcher_id()
- .ok_or(BpfmanError::DispatcherNotRequired)?;
-
- let next_available_id = self.dispatchers.attached_programs(&did);
- if next_available_id >= 10 {
- return Err(BpfmanError::TooManyPrograms);
- }
-
- debug!("next_available_id={next_available_id}");
-
- let program_type = program.kind();
- let if_index = program.if_index();
- let if_name = program.if_name().unwrap();
- let direction = program.direction();
-
- self.programs.set_program_positions(program, true);
-
- let mut programs: Vec<&mut Program> = self
- .programs
- .programs_mut(&program_type, &if_index, &direction)
- .collect::>();
-
- // add the program that's being loaded
- programs.push(program);
-
- let old_dispatcher = self.dispatchers.remove(&did);
- let if_config = if let Some(ref i) = self.config.interfaces {
- i.get(&if_name)
- } else {
- None
- };
- let next_revision = if let Some(ref old) = old_dispatcher {
- old.next_revision()
- } else {
- 1
- };
-
- let dispatcher = Dispatcher::new(
- if_config,
- &mut programs,
- next_revision,
- old_dispatcher,
- self.image_manager.clone(),
- )
- .await
- .or_else(|e| {
- // If kernel ID was never set there's no pins to cleanup here so just continue
- if let Some(info) = program.kernel_info() {
- program
- .delete(info.id)
- .map_err(BpfmanError::BpfmanProgramDeleteError)?;
- }
- Err(e)
- })?;
-
- self.dispatchers.insert(did, dispatcher);
- let id = program
- .kernel_info()
- .expect("kernel info should be set after load")
- .id;
-
- program.set_attached();
- program
- .save(id)
- .map_err(|e| BpfmanError::Error(format!("unable to save program state: {e}")))?;
-
- Ok(id)
- }
-
- pub(crate) async fn add_single_attach_program(
- &mut self,
- p: &mut Program,
- ) -> Result {
- debug!("BpfManager::add_single_attach_program()");
- let name = p.data()?.name();
- let mut bpf = BpfLoader::new();
-
- for (key, value) in p.data()?.global_data() {
- bpf.set_global(key, value.as_slice(), true);
- }
-
- // If map_pin_path is set already it means we need to use a pin
- // path which should already exist on the system.
- if let Some(map_pin_path) = p.data()?.map_pin_path() {
- debug!(
- "single-attach program {name} is using maps from {:?}",
- map_pin_path
- );
- bpf.map_pin_path(map_pin_path);
- }
-
- let mut loader = bpf
- .allow_unsupported_maps()
- .load(p.data()?.program_bytes())?;
-
- let raw_program = loader
- .program_mut(name)
- .ok_or(BpfmanError::BpfFunctionNameNotValid(name.to_owned()))?;
-
- let res = match p {
- Program::Tracepoint(ref mut program) => {
- let parts: Vec<&str> = program.tracepoint.split('/').collect();
- if parts.len() != 2 {
- return Err(BpfmanError::InvalidAttach(program.tracepoint.to_string()));
- }
- let category = parts[0].to_owned();
- let name = parts[1].to_owned();
-
- let tracepoint: &mut TracePoint = raw_program.try_into()?;
-
- tracepoint.load()?;
- program
- .data
- .set_kernel_info(Some(tracepoint.info()?.try_into()?));
-
- let link_id = tracepoint.attach(&category, &name)?;
-
- let owned_link: TracePointLink = tracepoint.take_link(link_id)?;
- let fd_link: FdLink = owned_link
- .try_into()
- .expect("unable to get owned tracepoint attach link");
-
- let id = program.data.id().expect("id should be set after load");
-
- fd_link
- .pin(format!("{RTDIR_FS}/prog_{}_link", id))
- .map_err(BpfmanError::UnableToPinLink)?;
-
- tracepoint
- .pin(format!("{RTDIR_FS}/prog_{id}"))
- .map_err(BpfmanError::UnableToPinProgram)?;
-
- Ok(id)
- }
- Program::Kprobe(ref mut program) => {
- let requested_probe_type = match program.retprobe {
- true => Kretprobe,
- false => Kprobe,
- };
-
- if requested_probe_type == Kretprobe && program.offset != 0 {
- return Err(BpfmanError::Error(format!(
- "offset not allowed for {Kretprobe}"
- )));
- }
-
- let kprobe: &mut KProbe = raw_program.try_into()?;
- kprobe.load()?;
-
- // verify that the program loaded was the same type as the
- // user requested
- let loaded_probe_type = ProbeType::from(kprobe.kind());
- if requested_probe_type != loaded_probe_type {
- return Err(BpfmanError::Error(format!(
- "expected {requested_probe_type}, loaded program is {loaded_probe_type}"
- )));
- }
-
- program
- .data
- .set_kernel_info(Some(kprobe.info()?.try_into()?));
-
- let link_id = kprobe.attach(program.fn_name.as_str(), program.offset)?;
-
- let owned_link: KProbeLink = kprobe.take_link(link_id)?;
- let fd_link: FdLink = owned_link
- .try_into()
- .expect("unable to get owned kprobe attach link");
-
- let id = program.data.id().expect("id should be set after load");
-
- fd_link
- .pin(format!("{RTDIR_FS}/prog_{}_link", id))
- .map_err(BpfmanError::UnableToPinLink)?;
-
- kprobe
- .pin(format!("{RTDIR_FS}/prog_{id}"))
- .map_err(BpfmanError::UnableToPinProgram)?;
-
- Ok(id)
- }
- Program::Uprobe(ref mut program) => {
- let requested_probe_type = match program.retprobe {
- true => Uretprobe,
- false => Uprobe,
- };
-
- let uprobe: &mut UProbe = raw_program.try_into()?;
- uprobe.load()?;
-
- // verify that the program loaded was the same type as the
- // user requested
- let loaded_probe_type = ProbeType::from(uprobe.kind());
- if requested_probe_type != loaded_probe_type {
- return Err(BpfmanError::Error(format!(
- "expected {requested_probe_type}, loaded program is {loaded_probe_type}"
- )));
- }
-
- program
- .data
- .set_kernel_info(Some(uprobe.info()?.try_into()?));
-
- let link_id = uprobe.attach(
- program.fn_name.as_deref(),
- program.offset,
- program.target.clone(),
- program.pid,
- )?;
-
- let owned_link: UProbeLink = uprobe.take_link(link_id)?;
- let fd_link: FdLink = owned_link
- .try_into()
- .expect("unable to get owned uprobe attach link");
-
- let id = program.data.id().expect("id should be set after load");
-
- fd_link
- .pin(format!("{RTDIR_FS}/prog_{}_link", id))
- .map_err(BpfmanError::UnableToPinLink)?;
-
- uprobe
- .pin(format!("{RTDIR_FS}/prog_{id}"))
- .map_err(BpfmanError::UnableToPinProgram)?;
-
- Ok(id)
- }
- _ => panic!("not a supported single attach program"),
- };
-
- match res {
- Ok(id) => {
- // If this program is the map(s) owner pin all maps (except for .rodata and .bss) by name.
- if p.data()?.map_pin_path().is_none() {
- let map_pin_path = calc_map_pin_path(id);
- p.data_mut()?.set_map_pin_path(Some(map_pin_path.clone()));
- create_map_pin_path(&map_pin_path).await?;
-
- for (name, map) in loader.maps_mut() {
- if !should_map_be_pinned(name) {
- continue;
- }
- debug!(
- "Pinning map: {name} to path: {}",
- map_pin_path.join(name).display()
- );
- map.pin(map_pin_path.join(name))
- .map_err(BpfmanError::UnableToPinMap)?;
- }
- }
-
- p.save(id)
- // we might want to log or ignore this error instead of returning here...
- // because otherwise it will hide the original error (from res above)
- .map_err(|_| {
- BpfmanError::Error("unable to persist program data".to_string())
- })?;
- }
- Err(_) => {
- // If kernel ID was never set there's no pins to cleanup here so just continue
- if let Some(info) = p.kernel_info() {
- p.delete(info.id)
- .map_err(BpfmanError::BpfmanProgramDeleteError)?;
- };
- }
- };
-
- res
- }
-
- pub(crate) async fn remove_program(&mut self, id: u32) -> Result<(), BpfmanError> {
- info!("Removing program with id: {id}");
- let mut prog = match self.programs.remove(&id) {
- Some(p) => p,
- None => {
- return Err(BpfmanError::Error(format!(
- "Program {0} does not exist or was not created by bpfman",
- id,
- )));
- }
- };
-
- let map_owner_id = prog.data()?.map_owner_id();
-
- prog.delete(id)
- .map_err(BpfmanError::BpfmanProgramDeleteError)?;
-
- match prog {
- Program::Xdp(_) | Program::Tc(_) => self.remove_multi_attach_program(&mut prog).await?,
- Program::Tracepoint(_)
- | Program::Kprobe(_)
- | Program::Uprobe(_)
- | Program::Unsupported(_) => (),
- }
-
- self.delete_map(id, map_owner_id).await?;
- Ok(())
- }
-
- pub(crate) async fn remove_multi_attach_program(
- &mut self,
- program: &mut Program,
- ) -> Result<(), BpfmanError> {
- debug!("BpfManager::remove_multi_attach_program()");
-
- let did = program
- .dispatcher_id()
- .ok_or(BpfmanError::DispatcherNotRequired)?;
-
- let next_available_id = self.dispatchers.attached_programs(&did) - 1;
- debug!("next_available_id = {next_available_id}");
-
- let mut old_dispatcher = self.dispatchers.remove(&did);
-
- if let Some(ref mut old) = old_dispatcher {
- if next_available_id == 0 {
- // Delete the dispatcher
- return old.delete(true);
- }
- }
-
- self.programs.set_program_positions(program, false);
-
- let program_type = program.kind();
- let if_index = program.if_index();
- let if_name = program.if_name().unwrap();
- let direction = program.direction();
-
- // Intentionally don't add filter program here
- let mut programs: Vec<&mut Program> = self
- .programs
- .programs_mut(&program_type, &if_index, &direction)
- .collect();
-
- let if_config = if let Some(ref i) = self.config.interfaces {
- i.get(&if_name)
- } else {
- None
- };
- let next_revision = if let Some(ref old) = old_dispatcher {
- old.next_revision()
- } else {
- 1
- };
- debug!("next_revision = {next_revision}");
- let dispatcher = Dispatcher::new(
- if_config,
- &mut programs,
- next_revision,
- old_dispatcher,
- self.image_manager.clone(),
- )
- .await?;
- self.dispatchers.insert(did, dispatcher);
- Ok(())
- }
-
- pub(crate) async fn rebuild_multiattach_dispatcher(
- &mut self,
- mut filter_prog: Program,
- did: DispatcherId,
- ) -> Result<(), BpfmanError> {
- let program_type = filter_prog.kind();
- let if_index = filter_prog.if_index();
- let direction = filter_prog.direction();
-
- debug!("BpfManager::rebuild_multiattach_dispatcher() for program type {program_type} on if_index {if_index:?}");
- let mut old_dispatcher = self.dispatchers.remove(&did);
-
- if let Some(ref mut old) = old_dispatcher {
- debug!("Rebuild Multiattach Dispatcher for {did:?}");
- self.programs.set_program_positions(&mut filter_prog, false);
-
- let mut programs: Vec<&mut Program> = self
- .programs
- .programs_mut(&program_type, &if_index, &direction)
- .collect();
-
- debug!("programs loaded: {}", programs.len());
-
- // The following checks should have been done when the dispatcher was built, but check again to confirm
- if programs.is_empty() {
- return old.delete(true);
- } else if programs.len() > 10 {
- return Err(BpfmanError::TooManyPrograms);
- }
-
- let if_name = old.if_name();
- let if_config = if let Some(ref i) = self.config.interfaces {
- i.get(&if_name)
- } else {
- None
- };
-
- let next_revision = if let Some(ref old) = old_dispatcher {
- old.next_revision()
- } else {
- 1
- };
-
- let dispatcher = Dispatcher::new(
- if_config,
- &mut programs,
- next_revision,
- old_dispatcher,
- self.image_manager.clone(),
- )
- .await?;
- self.dispatchers.insert(did, dispatcher);
- } else {
- debug!("No dispatcher found in rebuild_multiattach_dispatcher() for {did:?}");
- }
- Ok(())
- }
-
- pub(crate) fn list_programs(&mut self) -> Result, BpfmanError> {
- debug!("BpfManager::list_programs()");
-
- // Get an iterator for the bpfman load programs, a hash map indexed by program id.
- let mut bpfman_progs: HashMap = self.programs.get_programs_iter().collect();
-
- // Call Aya to get ALL the loaded eBPF programs, and loop through each one.
- loaded_programs()
- .map(|p| {
- let prog = p.map_err(BpfmanError::BpfProgramError)?;
- let prog_id = prog.id();
-
- // If the program was loaded by bpfman (check the hash map), then us it.
- // Otherwise, convert the data returned from Aya into an Unsupported Program Object.
- match bpfman_progs.remove(&prog_id) {
- Some(p) => Ok(p.to_owned()),
- None => Ok(Program::Unsupported(prog.try_into()?)),
- }
- })
- .collect()
- }
-
- pub(crate) fn get_program(&mut self, id: u32) -> Result {
- debug!("Getting program with id: {id}");
- // If the program was loaded by bpfman, then use it.
- // Otherwise, call Aya to get ALL the loaded eBPF programs, and convert the data
- // returned from Aya into an Unsupported Program Object.
- match self.programs.get(&id) {
- Some(p) => Ok(p.to_owned()),
- None => loaded_programs()
- .find_map(|p| {
- let prog = p.ok()?;
- if prog.id() == id {
- Some(Program::Unsupported(prog.try_into().ok()?))
- } else {
- None
- }
- })
- .ok_or(BpfmanError::Error(format!(
- "Program {0} does not exist",
- id
- ))),
- }
- }
-
- async fn pull_bytecode(&self, args: PullBytecodeArgs) -> anyhow::Result<()> {
- let (tx, rx) = oneshot::channel();
- self.image_manager
- .send(ImageManagerCommand::Pull {
- image: args.image.image_url,
- pull_policy: args.image.image_pull_policy.clone(),
- username: args.image.username.clone(),
- password: args.image.password.clone(),
- resp: tx,
- })
- .await?;
- let res = match rx.await? {
- Ok(_) => {
- info!("Successfully pulled bytecode");
- Ok(())
- }
- Err(e) => Err(e).map_err(|e| BpfmanError::BpfBytecodeError(e.into())),
- };
- let _ = args.responder.send(res);
- Ok(())
- }
-
- pub(crate) async fn process_commands(&mut self) {
- loop {
- // Start receiving messages
- select! {
- biased;
- _ = shutdown_handler() => {
- info!("Signal received to stop command processing");
- break;
- }
- Some(cmd) = self.commands.recv() => {
- match cmd {
- Command::Load(args) => {
- let prog = self.add_program(args.program).await;
- // Ignore errors as they'll be propagated to caller in the RPC status
- let _ = args.responder.send(prog);
- },
- Command::Unload(args) => self.unload_command(args).await.unwrap(),
- Command::List { responder } => {
- let progs = self.list_programs();
- // Ignore errors as they'll be propagated to caller in the RPC status
- let _ = responder.send(progs);
- }
- Command::Get(args) => {
- let prog = self.get_program(args.id);
- // Ignore errors as they'll be propagated to caller in the RPC status
- let _ = args.responder.send(prog);
- },
- Command::PullBytecode (args) => self.pull_bytecode(args).await.unwrap(),
- }
- }
- }
- }
- info!("Stopping processing commands");
- }
-
- async fn unload_command(&mut self, args: UnloadArgs) -> anyhow::Result<()> {
- let res = self.remove_program(args.id).await;
- // Ignore errors as they'll be propagated to caller in the RPC status
- let _ = args.responder.send(res);
- Ok(())
- }
-
- // This function checks to see if the user provided map_owner_id is valid.
- fn is_map_owner_id_valid(&mut self, map_owner_id: u32) -> Result {
- let map_pin_path = calc_map_pin_path(map_owner_id);
-
- if self.maps.contains_key(&map_owner_id) {
- // Return the map_pin_path
- return Ok(map_pin_path);
- }
- Err(BpfmanError::Error(
- "map_owner_id does not exists".to_string(),
- ))
- }
-
- // This function is called if the program's map directory was created,
- // but the eBPF program failed to load. save_map() has not been called,
- // so self.maps has not been updated for this program.
- // If the user provided a ID of program to share a map with,
- // then map the directory is still in use and there is nothing to do.
- // Otherwise, the map directory was created so it must
- // deleted.
- async fn cleanup_map_pin_path(
- &mut self,
- map_pin_path: &Path,
- map_owner_id: Option,
- ) -> Result<(), BpfmanError> {
- if map_owner_id.is_none() {
- let _ = remove_dir_all(map_pin_path)
- .await
- .map_err(|e| BpfmanError::Error(format!("can't delete map dir: {e}")));
- Ok(())
- } else {
- Ok(())
- }
- }
-
- // This function writes the map to the map hash table. If this eBPF
- // program is the map owner, then a new entry is add to the map hash
- // table and permissions on the directory are updated to grant bpfman
- // user group access to all the maps in the directory. If this eBPF
- // program is not the owner, then the eBPF program ID is added to
- // the Used-By array.
-
- // TODO this should probably be program.save_map not bpfmanager.save_map
- async fn save_map(
- &mut self,
- program: &mut Program,
- id: u32,
- map_owner_id: Option,
- ) -> Result<(), BpfmanError> {
- let data = program.data_mut()?;
-
- match map_owner_id {
- Some(m) => {
- if let Some(map) = self.maps.get_mut(&m) {
- map.used_by.push(id);
-
- // This program has no been inserted yet, so set map_used_by to
- // newly updated list.
- data.set_maps_used_by(Some(map.used_by.clone()));
-
- // Update all the programs using the same map with the updated map_used_by.
- for used_by_id in map.used_by.iter() {
- if let Some(program) = self.programs.get_mut(used_by_id) {
- if let Ok(data) = program.data_mut() {
- data.set_maps_used_by(Some(map.used_by.clone()));
- } else {
- return Err(BpfmanError::Error(
- "unable to retrieve data for {id}".to_string(),
- ));
- }
- }
- }
- } else {
- return Err(BpfmanError::Error(
- "map_owner_id does not exists".to_string(),
- ));
- }
- }
- None => {
- let map = BpfMap { used_by: vec![id] };
-
- self.maps.insert(id, map);
-
- // Update this program with the updated map_used_by
- data.set_maps_used_by(Some(vec![id]));
-
- // Set the permissions on the map_pin_path directory.
- if let Some(map_pin_path) = data.map_pin_path() {
- if let Some(path) = map_pin_path.to_str() {
- debug!("bpf set dir permissions for {}", path);
- set_dir_permissions(path, MAPS_MODE).await;
- } else {
- return Err(BpfmanError::Error(format!(
- "invalid map_pin_path {} for {}",
- map_pin_path.display(),
- id
- )));
- }
- } else {
- return Err(BpfmanError::Error(format!(
- "map_pin_path should be set for {}",
- id
- )));
- }
- }
- }
-
- Ok(())
- }
-
- // This function cleans up a map entry when an eBPF program is
- // being unloaded. If the eBPF program is the map owner, then
- // the map is removed from the hash table and the associated
- // directory is removed. If this eBPF program is referencing a
- // map from another eBPF program, then this eBPF programs ID
- // is removed from the UsedBy array.
- async fn delete_map(&mut self, id: u32, map_owner_id: Option) -> Result<(), BpfmanError> {
- let index = match map_owner_id {
- Some(i) => i,
- None => id,
- };
-
- if let Some(map) = self.maps.get_mut(&index.clone()) {
- if let Some(index) = map.used_by.iter().position(|value| *value == id) {
- map.used_by.swap_remove(index);
- }
-
- if map.used_by.is_empty() {
- // No more programs using this map, so remove the entry from the map list.
- let path = calc_map_pin_path(index);
- self.maps.remove(&index.clone());
- remove_dir_all(path)
- .await
- .map_err(|e| BpfmanError::Error(format!("can't delete map dir: {e}")))?;
- } else {
- // Update all the programs still using the same map with the updated map_used_by.
- for id in map.used_by.iter() {
- if let Some(program) = self.programs.get_mut(id) {
- if let Ok(data) = program.data_mut() {
- data.set_maps_used_by(Some(map.used_by.clone()));
- }
- }
- }
- }
- } else {
- return Err(BpfmanError::Error(
- "map_pin_path does not exists".to_string(),
- ));
- }
-
- Ok(())
- }
-
- async fn rebuild_map_entry(&mut self, id: u32, program: &mut Program) {
- let map_owner_id = match program.data() {
- Ok(data) => data.map_owner_id(),
- Err(_) => {
- warn!("unable to retrieve data for {id} retrieving map_owner_id on rebuild");
- return;
- }
- };
- let index = match map_owner_id {
- Some(i) => i,
- None => id,
- };
-
- if let Some(map) = self.maps.get_mut(&index) {
- map.used_by.push(id);
-
- // This program has not been inserted yet, so update it with the
- // updated map_used_by.
- if let Ok(data) = program.data_mut() {
- data.set_maps_used_by(Some(map.used_by.clone()));
- } else {
- warn!("unable to retrieve data for {id} during rebuild of maps");
- }
-
- // Update all the other programs using the same map with the updated map_used_by.
- for used_by_id in map.used_by.iter() {
- // program may not exist yet on rebuild, so ignore if not there
- if let Some(prog) = self.programs.get_mut(used_by_id) {
- if let Ok(data) = prog.data_mut() {
- data.set_maps_used_by(Some(map.used_by.clone()));
- } else {
- warn!("unable to retrieve data for {used_by_id} when setting map_used_by on rebuild");
- }
- }
- }
- } else {
- let map = BpfMap { used_by: vec![id] };
- self.maps.insert(index, map);
-
- if let Ok(data) = program.data_mut() {
- data.set_maps_used_by(Some(vec![id]));
- } else {
- warn!("unable to retrieve data for {id} during rebuild of maps");
- }
- }
- }
-}
-
-// map_pin_path is a the directory the maps are located. Currently, it
-// is a fixed bpfman location containing the map_index, which is a ID.
-// The ID is either the programs ID, or the ID of another program
-// that map_owner_id references.
-pub fn calc_map_pin_path(id: u32) -> PathBuf {
- PathBuf::from(format!("{RTDIR_FS_MAPS}/{}", id))
-}
-
-// Create the map_pin_path for a given program.
-pub async fn create_map_pin_path(p: &Path) -> Result<(), BpfmanError> {
- create_dir_all(p)
- .await
- .map_err(|e| BpfmanError::Error(format!("can't create map dir: {e}")))
-}
diff --git a/bpfman/src/cli/get.rs b/bpfman/src/cli/get.rs
deleted file mode 100644
index 9c7e4a706..000000000
--- a/bpfman/src/cli/get.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-// Copyright Authors of bpfman
-
-use bpfman_api::{
- config::Config,
- v1::{bpfman_client::BpfmanClient, GetRequest},
-};
-use clap::Args;
-
-use crate::cli::{select_channel, table::ProgTable};
-
-#[derive(Args, Debug)]
-pub(crate) struct GetArgs {
- /// Required: Program id to get.
- id: u32,
-}
-
-pub(crate) fn execute_get(args: &GetArgs, config: &mut Config) -> Result<(), anyhow::Error> {
- tokio::runtime::Builder::new_multi_thread()
- .enable_all()
- .build()
- .unwrap()
- .block_on(async {
- let channel = select_channel(config).expect("failed to select channel");
- let mut client = BpfmanClient::new(channel);
- let request = tonic::Request::new(GetRequest { id: args.id });
- let response = client.get(request).await?.into_inner();
-
- ProgTable::new_get_bpfman(&response.info)?.print();
- ProgTable::new_get_unsupported(&response.kernel_info)?.print();
- Ok::<(), anyhow::Error>(())
- })
-}
diff --git a/bpfman/src/cli/image.rs b/bpfman/src/cli/image.rs
deleted file mode 100644
index bf948f561..000000000
--- a/bpfman/src/cli/image.rs
+++ /dev/null
@@ -1,86 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-// Copyright Authors of bpfman
-
-use base64::{engine::general_purpose, Engine};
-use bpfman_api::{
- config::Config,
- v1::{bpfman_client::BpfmanClient, BytecodeImage, PullBytecodeRequest},
- ImagePullPolicy,
-};
-use clap::{Args, Subcommand};
-
-use crate::cli::select_channel;
-
-#[derive(Subcommand, Debug)]
-pub(crate) enum ImageSubCommand {
- /// Pull an eBPF bytecode image from a remote registry.
- Pull(PullBytecodeArgs),
-}
-
-impl ImageSubCommand {
- pub(crate) fn execute(&self, config: &mut Config) -> anyhow::Result<()> {
- match self {
- ImageSubCommand::Pull(args) => execute_pull(args, config),
- }
- }
-}
-
-#[derive(Args, Debug)]
-pub(crate) struct PullBytecodeArgs {
- /// Required: Container Image URL.
- /// Example: --image-url quay.io/bpfman-bytecode/xdp_pass:latest
- #[clap(short, long, verbatim_doc_comment)]
- pub(crate) image_url: String,
-
- /// Optional: Registry auth for authenticating with the specified image registry.
- /// This should be base64 encoded from the ':' string just like
- /// it's stored in the docker/podman host config.
- /// Example: --registry_auth "YnjrcKw63PhDcQodiU9hYxQ2"
- #[clap(short, long, verbatim_doc_comment)]
- registry_auth: Option,
-
- /// Optional: Pull policy for remote images.
- ///
- /// [possible values: Always, IfNotPresent, Never]
- #[clap(short, long, verbatim_doc_comment, default_value = "IfNotPresent")]
- pull_policy: String,
-}
-
-impl TryFrom<&PullBytecodeArgs> for BytecodeImage {
- type Error = anyhow::Error;
-
- fn try_from(value: &PullBytecodeArgs) -> Result {
- let pull_policy: ImagePullPolicy = value.pull_policy.as_str().try_into()?;
- let (username, password) = match &value.registry_auth {
- Some(a) => {
- let auth_raw = general_purpose::STANDARD.decode(a)?;
- let auth_string = String::from_utf8(auth_raw)?;
- let (username, password) = auth_string.split_once(':').unwrap();
- (username.to_owned(), password.to_owned())
- }
- None => ("".to_owned(), "".to_owned()),
- };
-
- Ok(BytecodeImage {
- url: value.image_url.clone(),
- image_pull_policy: pull_policy.into(),
- username: Some(username),
- password: Some(password),
- })
- }
-}
-
-pub(crate) fn execute_pull(args: &PullBytecodeArgs, config: &mut Config) -> anyhow::Result<()> {
- tokio::runtime::Builder::new_multi_thread()
- .enable_all()
- .build()
- .unwrap()
- .block_on(async {
- let channel = select_channel(config).expect("failed to select channel");
- let mut client = BpfmanClient::new(channel);
- let image: BytecodeImage = args.try_into()?;
- let request = tonic::Request::new(PullBytecodeRequest { image: Some(image) });
- let _response = client.pull_bytecode(request).await?;
- Ok::<(), anyhow::Error>(())
- })
-}
diff --git a/bpfman/src/cli/list.rs b/bpfman/src/cli/list.rs
deleted file mode 100644
index 50627c14c..000000000
--- a/bpfman/src/cli/list.rs
+++ /dev/null
@@ -1,76 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-// Copyright Authors of bpfman
-
-use anyhow::bail;
-use bpfman_api::{
- config::Config,
- v1::{bpfman_client::BpfmanClient, ListRequest},
- ProgramType,
-};
-use clap::Args;
-
-use crate::cli::{parse_key_val, select_channel, table::ProgTable};
-
-#[derive(Args, Debug)]
-pub(crate) struct ListArgs {
- /// Optional: List a specific program type
- /// Example: --program-type xdp
- ///
- /// [possible values: unspec, socket-filter, kprobe, tc, sched-act,
- /// tracepoint, xdp, perf-event, cgroup-skb,
- /// cgroup-sock, lwt-in, lwt-out, lwt-xmit, sock-ops,
- /// sk-skb, cgroup-device, sk-msg, raw-tracepoint,
- /// cgroup-sock-addr, lwt-seg6-local, lirc-mode2,
- /// sk-reuseport, flow-dissector, cgroup-sysctl,
- /// raw-tracepoint-writable, cgroup-sockopt, tracing,
- /// struct-ops, ext, lsm, sk-lookup, syscall]
- #[clap(short, long, verbatim_doc_comment, hide_possible_values = true)]
- program_type: Option,
-
- /// Optional: List programs which contain a specific set of metadata labels
- /// that were applied when the program was loaded with `--metadata` parameter.
- /// Format: =
- ///
- /// Example: --metadata-selector owner=acme
- #[clap(short, long, verbatim_doc_comment, value_parser=parse_key_val, value_delimiter = ',')]
- metadata_selector: Option