diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ef021983..c8cb28b3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,10 +5,10 @@ updates: schedule: interval: "daily" groups: - dep-updates: + actions-updates: dependency-type: "production" applies-to: "version-updates" - dev-updates: + actions-dev-updates: dependency-type: "development" applies-to: "version-updates" - package-ecosystem: "cargo" @@ -27,9 +27,9 @@ updates: schedule: interval: "daily" groups: - dep-updates: + docker-updates: dependency-type: "production" applies-to: "version-updates" - dev-updates: + docker-dev-updates: dependency-type: "development" applies-to: "version-updates" diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 10e13a3c..28171c05 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -126,76 +126,3 @@ jobs: run: ./hack/ci/install-linux-deps.sh - name: cargo clippy run: ./hack/build/cargo.sh clippy - zone-initrd: - runs-on: ubuntu-latest - strategy: - matrix: - arch: - - x86_64 - - aarch64 - env: - TARGET_ARCH: "${{ matrix.arch }}" - name: zone initrd linux-${{ matrix.arch }} - steps: - - name: harden runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 - with: - egress-policy: audit - - name: checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - submodules: recursive - - name: install stable rust toolchain with ${{ matrix.arch }}-unknown-linux-gnu and ${{ matrix.arch }}-unknown-linux-musl rust targets - run: | - rustup update --no-self-update stable - rustup default stable - rustup target add ${{ matrix.arch }}-unknown-linux-gnu ${{ matrix.arch }}-unknown-linux-musl - - name: install linux dependencies - run: ./hack/ci/install-linux-deps.sh - - name: initrd build - run: ./hack/initrd/build.sh - kratactl-build: - strategy: - fail-fast: false - matrix: - platform: - - { os: linux, arch: x86_64, on: ubuntu-latest, deps: linux } - - { os: linux, arch: aarch64, on: ubuntu-latest, deps: linux } - - { os: darwin, arch: x86_64, on: macos-14, deps: darwin } - - { os: darwin, arch: aarch64, on: macos-14, deps: darwin } - - { os: freebsd, arch: x86_64, on: ubuntu-latest, deps: linux } - - { os: windows, arch: x86_64, on: windows-latest, deps: windows } - env: - TARGET_OS: "${{ matrix.platform.os }}" - TARGET_ARCH: "${{ matrix.platform.arch }}" - runs-on: "${{ matrix.platform.on }}" - name: kratactl build ${{ matrix.platform.os }}-${{ matrix.platform.arch }} - defaults: - run: - shell: bash - steps: - - name: harden runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 - with: - egress-policy: audit - - name: configure git line endings - run: git config --global core.autocrlf false && git config --global core.eol lf - if: ${{ matrix.platform.os == 'windows' }} - - name: checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - submodules: recursive - - name: install stable rust toolchain - run: | - rustup update --no-self-update stable - rustup default stable - - name: install ${{ matrix.platform.arch }}-apple-darwin rust target - run: "rustup target add --toolchain stable ${{ matrix.platform.arch }}-apple-darwin" - if: ${{ matrix.platform.os == 'darwin' }} - - name: setup homebrew - uses: homebrew/actions/setup-homebrew@4b34604e75af8f8b23b454f0b5ffb7c5d8ce0056 # master - if: ${{ matrix.platform.os == 'darwin' }} - - name: install ${{ matrix.platform.deps }} dependencies - run: ./hack/ci/install-${{ matrix.platform.deps }}-deps.sh - - name: cargo build kratactl - run: ./hack/build/cargo.sh build --bin kratactl diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml deleted file mode 100644 index c75a6f69..00000000 --- a/.github/workflows/nightly.yml +++ /dev/null @@ -1,165 +0,0 @@ -name: nightly -on: - workflow_dispatch: - schedule: - - cron: "0 10 * * *" -permissions: - contents: read -jobs: - full-build: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - arch: - - x86_64 - - aarch64 - env: - TARGET_ARCH: "${{ matrix.arch }}" - CI_NEEDS_FPM: "1" - name: nightly full build linux-${{ matrix.arch }} - steps: - - name: harden runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 - with: - egress-policy: audit - - name: checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - submodules: recursive - - name: install stable rust toolchain with ${{ matrix.arch }}-unknown-linux-gnu and ${{ matrix.arch }}-unknown-linux-musl rust targets - run: | - rustup update --no-self-update stable - rustup default stable - rustup target add ${{ matrix.arch }}-unknown-linux-gnu ${{ matrix.arch }}-unknown-linux-musl - - name: install linux dependencies - run: ./hack/ci/install-linux-deps.sh - - name: build systemd bundle - run: ./hack/dist/bundle.sh - - name: upload systemd bundle - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 - with: - name: krata-bundle-systemd-${{ matrix.arch }} - path: "target/dist/bundle-systemd-${{ matrix.arch }}.tgz" - compression-level: 0 - - name: build deb package - run: ./hack/dist/deb.sh - - name: upload deb package - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 - with: - name: krata-debian-${{ matrix.arch }} - path: "target/dist/*.deb" - compression-level: 0 - - name: build apk package - run: ./hack/dist/apk.sh - - name: upload apk package - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 - with: - name: krata-alpine-${{ matrix.arch }} - path: "target/dist/*_${{ matrix.arch }}.apk" - compression-level: 0 - kratactl-build: - strategy: - fail-fast: false - matrix: - platform: - - { os: linux, arch: x86_64, on: ubuntu-latest, deps: linux } - - { os: linux, arch: aarch64, on: ubuntu-latest, deps: linux } - - { os: darwin, arch: x86_64, on: macos-14, deps: darwin } - - { os: darwin, arch: aarch64, on: macos-14, deps: darwin } - - { os: freebsd, arch: x86_64, on: ubuntu-latest, deps: linux } - - { os: windows, arch: x86_64, on: windows-latest, deps: windows } - env: - TARGET_OS: "${{ matrix.platform.os }}" - TARGET_ARCH: "${{ matrix.platform.arch }}" - runs-on: "${{ matrix.platform.on }}" - name: nightly kratactl build ${{ matrix.platform.os }}-${{ matrix.platform.arch }} - defaults: - run: - shell: bash - steps: - - name: harden runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 - with: - egress-policy: audit - - name: configure git line endings - run: git config --global core.autocrlf false && git config --global core.eol lf - if: ${{ matrix.platform.os == 'windows' }} - - name: checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - submodules: recursive - - name: install stable rust toolchain - run: | - rustup update --no-self-update stable - rustup default stable - - name: install ${{ matrix.platform.arch }}-apple-darwin rust target - run: "rustup target add --toolchain stable ${{ matrix.platform.arch }}-apple-darwin" - if: ${{ matrix.platform.os == 'darwin' }} - - name: setup homebrew - uses: homebrew/actions/setup-homebrew@4b34604e75af8f8b23b454f0b5ffb7c5d8ce0056 # master - if: ${{ matrix.platform.os == 'darwin' }} - - name: install ${{ matrix.platform.deps }} dependencies - run: ./hack/ci/install-${{ matrix.platform.deps }}-deps.sh - - name: cargo build kratactl - run: ./hack/build/cargo.sh build --release --bin kratactl - - name: upload kratactl - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 - with: - name: kratactl-${{ matrix.platform.os }}-${{ matrix.platform.arch }} - path: "target/*/release/kratactl" - if: ${{ matrix.platform.os != 'windows' }} - - name: upload kratactl - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 - with: - name: kratactl-${{ matrix.platform.os }}-${{ matrix.platform.arch }} - path: "target/*/release/kratactl.exe" - if: ${{ matrix.platform.os == 'windows' }} - oci-build: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - component: - - kratactl - - kratad - - kratanet - - krata-zone - name: nightly oci build ${{ matrix.component }} - permissions: - contents: read - id-token: write - packages: write - steps: - - name: harden runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 - with: - egress-policy: audit - - name: checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - submodules: recursive - - name: install cosign - uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # v3.6.0 - - name: setup docker buildx - uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 - - name: login to container registry - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 - with: - registry: ghcr.io - username: "${{ github.actor }}" - password: "${{ secrets.GITHUB_TOKEN }}" - - name: docker build and push ${{ matrix.component }} - uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0 - id: push - with: - file: ./images/Dockerfile.${{ matrix.component }} - platforms: linux/amd64,linux/aarch64 - tags: "ghcr.io/edera-dev/${{ matrix.component }}:nightly" - push: true - - name: cosign sign ${{ matrix.component }} - run: cosign sign --yes "${TAGS}@${DIGEST}" - env: - DIGEST: "${{ steps.push.outputs.digest }}" - TAGS: "ghcr.io/edera-dev/${{ matrix.component }}:nightly" - COSIGN_EXPERIMENTAL: "true" diff --git a/.github/workflows/release-assets.yml b/.github/workflows/release-assets.yml deleted file mode 100644 index ac47cd3a..00000000 --- a/.github/workflows/release-assets.yml +++ /dev/null @@ -1,172 +0,0 @@ -name: release-assets -on: - release: - types: - - published -env: - CARGO_INCREMENTAL: 0 - CARGO_NET_GIT_FETCH_WITH_CLI: true - CARGO_NET_RETRY: 10 - CARGO_TERM_COLOR: always - RUST_BACKTRACE: 1 - RUSTUP_MAX_RETRIES: 10 -jobs: - services: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - arch: - - x86_64 - - aarch64 - env: - TARGET_ARCH: "${{ matrix.arch }}" - CI_NEEDS_FPM: "1" - name: release-assets services ${{ matrix.arch }} - permissions: - contents: write - steps: - - name: harden runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 - with: - egress-policy: audit - - name: checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - submodules: recursive - - name: install stable rust toolchain with ${{ matrix.arch }}-unknown-linux-gnu and ${{ matrix.arch }}-unknown-linux-musl rust targets - run: | - rustup update --no-self-update stable - rustup default stable - rustup target add ${{ matrix.arch }}-unknown-linux-gnu ${{ matrix.arch }}-unknown-linux-musl - - name: install linux dependencies - run: ./hack/ci/install-linux-deps.sh - - name: build systemd bundle - run: ./hack/dist/bundle.sh - - name: assemble systemd bundle - run: "./hack/ci/assemble-release-assets.sh bundle-systemd ${{ github.event.release.tag_name }} ${{ matrix.arch }} target/dist/bundle-systemd-${{ matrix.arch }}.tgz" - - name: build deb package - run: ./hack/dist/deb.sh - - name: assemble deb package - run: "./hack/ci/assemble-release-assets.sh debian ${{ github.event.release.tag_name }} ${{ matrix.arch }} target/dist/*.deb" - - name: build apk package - run: ./hack/dist/apk.sh - - name: assemble apk package - run: "./hack/ci/assemble-release-assets.sh alpine ${{ github.event.release.tag_name }} ${{ matrix.arch }} target/dist/*_${{ matrix.arch }}.apk" - - name: upload release artifacts - run: "./hack/ci/upload-release-assets.sh ${{ github.event.release.tag_name }}" - env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - kratactl: - strategy: - fail-fast: false - matrix: - platform: - - { os: linux, arch: x86_64, on: ubuntu-latest, deps: linux } - - { os: linux, arch: aarch64, on: ubuntu-latest, deps: linux } - - { os: darwin, arch: x86_64, on: macos-14, deps: darwin } - - { os: darwin, arch: aarch64, on: macos-14, deps: darwin } - - { os: freebsd, arch: x86_64, on: ubuntu-latest, deps: linux } - - { os: windows, arch: x86_64, on: windows-latest, deps: windows } - env: - TARGET_OS: "${{ matrix.platform.os }}" - TARGET_ARCH: "${{ matrix.platform.arch }}" - runs-on: "${{ matrix.platform.on }}" - name: release-assets kratactl ${{ matrix.platform.os }}-${{ matrix.platform.arch }} - defaults: - run: - shell: bash - timeout-minutes: 60 - permissions: - contents: write - steps: - - name: harden runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 - with: - egress-policy: audit - - name: checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - submodules: recursive - - name: install stable rust toolchain - run: | - rustup update --no-self-update stable - rustup default stable - - name: install ${{ matrix.platform.arch }}-apple-darwin rust target - run: "rustup target add --toolchain stable ${{ matrix.platform.arch }}-apple-darwin" - if: ${{ matrix.platform.os == 'darwin' }} - - name: setup homebrew - uses: homebrew/actions/setup-homebrew@4b34604e75af8f8b23b454f0b5ffb7c5d8ce0056 # master - if: ${{ matrix.platform.os == 'darwin' }} - - name: install ${{ matrix.platform.deps }} dependencies - run: ./hack/ci/install-${{ matrix.platform.deps }}-deps.sh - - name: cargo build kratactl - run: ./hack/build/cargo.sh build --release --bin kratactl - - name: assemble kratactl executable - run: "./hack/ci/assemble-release-assets.sh kratactl ${{ github.event.release.tag_name }} ${{ matrix.platform.os }}-${{ matrix.platform.arch }} target/*/release/kratactl" - if: ${{ matrix.platform.os != 'windows' }} - - name: assemble kratactl executable - run: "./hack/ci/assemble-release-assets.sh kratactl ${{ github.event.release.tag_name }} ${{ matrix.platform.os }}-${{ matrix.platform.arch }} target/*/release/kratactl.exe" - if: ${{ matrix.platform.os == 'windows' }} - - name: upload release artifacts - run: "./hack/ci/upload-release-assets.sh ${{ github.event.release.tag_name }}" - env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - oci: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - component: - - kratactl - - kratad - - kratanet - - krata-zone - name: release-assets oci ${{ matrix.component }} - permissions: - contents: read - id-token: write - packages: write - steps: - - name: harden runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 - with: - egress-policy: audit - - name: checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - submodules: recursive - - name: install cosign - uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # v3.6.0 - - name: setup docker buildx - uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 - - name: login to container registry - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 - with: - registry: ghcr.io - username: "${{ github.actor }}" - password: "${{ secrets.GITHUB_TOKEN }}" - - name: capture krata version - id: version - run: | - echo "KRATA_VERSION=$(./hack/dist/version.sh)" >> "${GITHUB_OUTPUT}" - - name: docker build and push ${{ matrix.component }} - uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0 - id: push - with: - file: ./images/Dockerfile.${{ matrix.component }} - platforms: linux/amd64,linux/aarch64 - tags: "ghcr.io/edera-dev/${{ matrix.component }}:${{ steps.version.outputs.KRATA_VERSION }},ghcr.io/edera-dev/${{ matrix.component }}:latest" - push: true - - name: cosign sign ${{ matrix.component }}:${{ steps.version.outputs.KRATA_VERSION }} - run: cosign sign --yes "${TAGS}@${DIGEST}" - env: - DIGEST: "${{ steps.push.outputs.digest }}" - TAGS: "ghcr.io/edera-dev/${{ matrix.component }}:${{ steps.version.outputs.KRATA_VERSION }}" - COSIGN_EXPERIMENTAL: "true" - - name: cosign sign ${{ matrix.component }}:latest - run: cosign sign --yes "${TAGS}@${DIGEST}" - env: - DIGEST: "${{ steps.push.outputs.digest }}" - TAGS: "ghcr.io/edera-dev/${{ matrix.component }}:latest" - COSIGN_EXPERIMENTAL: "true" diff --git a/CHANGELOG.md b/CHANGELOG.md index 02f7b6c1..ff662d73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,201 +7,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ## [0.0.21](https://github.com/edera-dev/krata/compare/v0.0.20...v0.0.21) - 2024-09-03 - -### Other -- update Cargo.lock dependencies - -## [0.0.20](https://github.com/edera-dev/krata/compare/v0.0.19...v0.0.20) - 2024-08-27 - -### Added -- *(krata)* implement network reservation list ([#366](https://github.com/edera-dev/krata/pull/366)) -- *(zone-exec)* implement terminal resize support ([#363](https://github.com/edera-dev/krata/pull/363)) - -### Other -- update Cargo.toml dependencies - -## [0.0.19](https://github.com/edera-dev/krata/compare/v0.0.18...v0.0.19) - 2024-08-25 - -### Added -- *(config)* write default config to config.toml on startup ([#356](https://github.com/edera-dev/krata/pull/356)) -- *(ctl)* add --format option to host status and improve cpu topology format ([#355](https://github.com/edera-dev/krata/pull/355)) - -### Fixed -- *(zone-exec)* ensure that the underlying process is killed when rpc is closed ([#361](https://github.com/edera-dev/krata/pull/361)) -- *(rpc)* rename HostStatus to GetHostStatus ([#360](https://github.com/edera-dev/krata/pull/360)) -- *(console)* don't replay history when attaching to the console ([#358](https://github.com/edera-dev/krata/pull/358)) -- *(zone-exec)* catch panic errors and show all errors immediately ([#359](https://github.com/edera-dev/krata/pull/359)) - -### Other -- *(control)* split out all of the rpc calls into their own files ([#357](https://github.com/edera-dev/krata/pull/357)) - -## [0.0.18](https://github.com/edera-dev/krata/compare/v0.0.17...v0.0.18) - 2024-08-22 - -### Added -- *(zone)* kernel command line control on launch ([#351](https://github.com/edera-dev/krata/pull/351)) -- *(xen-preflight)* test for hypervisor presence explicitly and error if missing ([#347](https://github.com/edera-dev/krata/pull/347)) - -### Fixed -- *(network)* allocate host ip from allocation pool ([#353](https://github.com/edera-dev/krata/pull/353)) -- *(daemon)* turn off trace logging ([#352](https://github.com/edera-dev/krata/pull/352)) - -### Other -- Add support for reading hypervisor console ([#344](https://github.com/edera-dev/krata/pull/344)) -- *(ctl)* move logic for branching ctl run steps into ControlCommands ([#342](https://github.com/edera-dev/krata/pull/342)) -- update Cargo.toml dependencies - -## [0.0.17](https://github.com/edera-dev/krata/compare/v0.0.16...v0.0.17) - 2024-08-15 - -### Added -- *(krata)* first pass on cpu hotplug support ([#340](https://github.com/edera-dev/krata/pull/340)) -- *(exec)* implement tty support (fixes [#335](https://github.com/edera-dev/krata/pull/335)) ([#336](https://github.com/edera-dev/krata/pull/336)) -- *(krata)* dynamic resource allocation (closes [#298](https://github.com/edera-dev/krata/pull/298)) ([#333](https://github.com/edera-dev/krata/pull/333)) - -### Other -- update Cargo.toml dependencies - -## [0.0.16](https://github.com/edera-dev/krata/compare/v0.0.15...v0.0.16) - 2024-08-14 - -### Added -- *(krata)* prepare for workload rework ([#276](https://github.com/edera-dev/krata/pull/276)) - -### Fixed -- *(idm)* reimplement packet processing algorithm ([#330](https://github.com/edera-dev/krata/pull/330)) -- *(power-trap-eacces)* gracefully handle hypercall errors in power management ([#325](https://github.com/edera-dev/krata/pull/325)) - -### Other -- *(o11y)* add more debug logs to daemon & runtime ([#318](https://github.com/edera-dev/krata/pull/318)) - -## [0.0.15](https://github.com/edera-dev/krata/compare/v0.0.14...v0.0.15) - 2024-08-06 - -### Fixed -- *(zone)* waitpid should be limited when no child processes exist (fixes [#304](https://github.com/edera-dev/krata/pull/304)) ([#305](https://github.com/edera-dev/krata/pull/305)) - -## [0.0.14](https://github.com/edera-dev/krata/compare/v0.0.13...v0.0.14) - 2024-08-06 - -### Added -- *(oci)* use local index as resolution cache when appropriate, fixes [#289](https://github.com/edera-dev/krata/pull/289) ([#294](https://github.com/edera-dev/krata/pull/294)) - -### Fixed -- *(idm)* process all idm messages in the same frame and use childwait exit notification for exec (fixes [#290](https://github.com/edera-dev/krata/pull/290)) ([#302](https://github.com/edera-dev/krata/pull/302)) - -### Other -- init: mount /proc with hidepid=1 ([#277](https://github.com/edera-dev/krata/pull/277)) -- update Cargo.toml dependencies - -## [0.0.13](https://github.com/edera-dev/krata/compare/v0.0.12...v0.0.13) - 2024-07-19 - -### Added -- *(kratactl)* rework cli to use subcommands ([#268](https://github.com/edera-dev/krata/pull/268)) -- *(krata)* rename guest to zone ([#266](https://github.com/edera-dev/krata/pull/266)) - -### Other -- *(deps)* upgrade dependencies, fix hyper io traits issue ([#252](https://github.com/edera-dev/krata/pull/252)) -- update Cargo.lock dependencies -- update Cargo.toml dependencies - -## [0.0.12](https://github.com/edera-dev/krata/compare/v0.0.11...v0.0.12) - 2024-07-12 - -### Added -- *(oci)* add configuration value for oci seed file ([#220](https://github.com/edera-dev/krata/pull/220)) -- *(power-management-defaults)* set an initial power management policy ([#219](https://github.com/edera-dev/krata/pull/219)) - -### Fixed -- *(daemon)* decrease rate of runtime reconcile ([#224](https://github.com/edera-dev/krata/pull/224)) -- *(power)* ensure that xeon cpus with cpu gaps are not detected as p/e compatible ([#218](https://github.com/edera-dev/krata/pull/218)) -- *(runtime)* use iommu only if devices are needed ([#243](https://github.com/edera-dev/krata/pull/243)) - -### Other -- Power management core functionality ([#217](https://github.com/edera-dev/krata/pull/217)) -- *(powermgmt)* disable for now as a hackfix ([#242](https://github.com/edera-dev/krata/pull/242)) -- Initial fluentd support ([#205](https://github.com/edera-dev/krata/pull/205)) -- update Cargo.toml dependencies -- Use native loopdev implementation instead of loopdev-3 ([#209](https://github.com/edera-dev/krata/pull/209)) - -## [0.0.11](https://github.com/edera-dev/krata/compare/v0.0.10...v0.0.11) - 2024-06-23 - -### Added -- pci passthrough ([#114](https://github.com/edera-dev/krata/pull/114)) -- *(runtime)* concurrent ip allocation ([#151](https://github.com/edera-dev/krata/pull/151)) -- *(xen)* dynamic platform architecture ([#194](https://github.com/edera-dev/krata/pull/194)) - -### Fixed -- *(oci)* remove file size limit ([#142](https://github.com/edera-dev/krata/pull/142)) -- *(oci)* use mirror.gcr.io as a mirror to docker hub ([#141](https://github.com/edera-dev/krata/pull/141)) - -### Other -- first pass of krata as an isolation engine -- *(xen)* split platform support into separate crate ([#195](https://github.com/edera-dev/krata/pull/195)) -- *(xen)* move device creation into transaction interface ([#196](https://github.com/edera-dev/krata/pull/196)) - -## [0.0.10](https://github.com/edera-dev/krata/compare/v0.0.9...v0.0.10) - 2024-04-22 - -### Added -- implement guest exec ([#107](https://github.com/edera-dev/krata/pull/107)) -- implement kernel / initrd oci image support ([#103](https://github.com/edera-dev/krata/pull/103)) -- idm v2 ([#102](https://github.com/edera-dev/krata/pull/102)) -- oci concurrency improvements ([#95](https://github.com/edera-dev/krata/pull/95)) -- oci tar format, bit-perfect disk storage for config and manifest, concurrent image pulls ([#88](https://github.com/edera-dev/krata/pull/88)) - -### Fixed -- oci cache store should fallback to copy when rename won't work ([#96](https://github.com/edera-dev/krata/pull/96)) - -### Other -- update Cargo.lock dependencies - -## [0.0.9](https://github.com/edera-dev/krata/compare/v0.0.8...v0.0.9) - 2024-04-15 - -### Added -- oci compliance work ([#85](https://github.com/edera-dev/krata/pull/85)) -- oci packer can now use mksquashfs if available ([#70](https://github.com/edera-dev/krata/pull/70)) -- basic kratactl top command ([#72](https://github.com/edera-dev/krata/pull/72)) -- idm snooping ([#71](https://github.com/edera-dev/krata/pull/71)) -- implement oci image progress ([#64](https://github.com/edera-dev/krata/pull/64)) -- guest metrics support ([#46](https://github.com/edera-dev/krata/pull/46)) - -### Other -- init: default to xterm if TERM is not set ([#52](https://github.com/edera-dev/krata/pull/52)) -- update Cargo.toml dependencies - -## [0.0.8](https://github.com/edera-dev/krata/compare/v0.0.7...v0.0.8) - 2024-04-09 - -### Other -- update Cargo.lock dependencies - -## [0.0.7](https://github.com/edera-dev/krata/compare/v0.0.6...v0.0.7) - 2024-04-09 - -### Other -- update Cargo.toml dependencies -- update Cargo.lock dependencies - -## [0.0.6](https://github.com/edera-dev/krata/compare/v0.0.5...v0.0.6) - 2024-04-09 - -### Fixed -- increase channel acquisition timeout to support lower performance hosts ([#36](https://github.com/edera-dev/krata/pull/36)) - -### Other -- update Cargo.toml dependencies -- update Cargo.lock dependencies - -## [0.0.5](https://github.com/edera-dev/krata/compare/v0.0.4...v0.0.5) - 2024-04-09 - -### Added -- *(ctl)* add help and about to commands and arguments ([#25](https://github.com/edera-dev/krata/pull/25)) - -### Other -- update Cargo.toml dependencies -- update Cargo.lock dependencies - -## [0.0.4](https://github.com/edera-dev/krata/releases/tag/v${version}) - 2024-04-03 - -### Other -- implement automatic releases -- reimplement console to utilize channels, and provide logs support -- set hostname from launch config -- implement event stream retries -- work on parallel reconciliation -- implement parallel guest reconciliation -- log when a guest start failures occurs -- remove device restriction -- setup loopback interface -- place running tasks in cgroup diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 759e5db4..2eca219a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,5 +30,4 @@ While it's totally fine to simply bring it up on our Discord, we encourage openi [Code of Conduct]: ./CODE_OF_CONDUCT.md [Security Policy]: ./SECURITY.md -[Development Guide]: ./DEV.md [good-first-issues]: https://github.com/edera-dev/krata/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22 diff --git a/Cargo.lock b/Cargo.lock index 3f4fe018..efedf817 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,18 +23,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -44,12 +32,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - [[package]] name = "anstream" version = "0.6.15" @@ -86,7 +68,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -96,59 +78,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys 0.52.0", -] - -[[package]] -name = "anyhow" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -dependencies = [ - "serde", -] - -[[package]] -name = "async-compression" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" -dependencies = [ - "flate2", - "futures-core", - "memchr", - "pin-project-lite", - "tokio", - "zstd", - "zstd-safe", -] - -[[package]] -name = "async-stream" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", + "windows-sys", ] [[package]] @@ -162,81 +92,12 @@ dependencies = [ "syn 2.0.74", ] -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" -[[package]] -name = "axum" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" -dependencies = [ - "async-trait", - "axum-core", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper 1.0.1", - "tower 0.4.13", - "tower-layer", - "tower-service", -] - -[[package]] -name = "axum-core" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper 0.1.2", - "tower-layer", - "tower-service", -] - -[[package]] -name = "backhand" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f2fc1bc7bb7fd449e02000cc1592cc63dcdcd61710f8b9efe32bab2d1784603" -dependencies = [ - "deku", - "flate2", - "rustc-hash 1.1.0", - "thiserror", - "tracing", - "xz2", - "zstd", - "zstd-safe", -] - [[package]] name = "backtrace" version = "0.3.73" @@ -252,51 +113,12 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - [[package]] name = "byteorder" version = "1.5.0" @@ -329,30 +151,11 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - -[[package]] -name = "castaway" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" -dependencies = [ - "rustversion", -] - [[package]] name = "cc" version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" -dependencies = [ - "jobserver", - "libc", -] [[package]] name = "cfg-if" @@ -367,3108 +170,463 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] -name = "cgroups-rs" -version = "0.3.4" +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "crc32fast" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db7c2f5545da4c12c5701455d9471da5f07db52e49b9cccb4f5512226dd0836" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ - "libc", - "log", - "nix 0.25.1", - "regex", - "thiserror", + "cfg-if", ] [[package]] -name = "circular-buffer" -version = "0.1.7" +name = "elf" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da987586004ae7c43b7df5e3f7693775068522e1086f8d9b2d74c778a0f43313" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" [[package]] -name = "clap" -version = "4.5.16" +name = "env_filter" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ - "clap_builder", - "clap_derive", + "log", + "regex", ] [[package]] -name = "clap_builder" -version = "4.5.15" +name = "env_logger" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.74", + "env_filter", + "humantime", + "log", ] [[package]] -name = "clap_lex" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" - -[[package]] -name = "colorchoice" -version = "1.0.2" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "comfy-table" -version = "7.1.1" +name = "flate2" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ - "crossterm 0.27.0", - "strum", - "strum_macros", - "unicode-width", + "crc32fast", + "miniz_oxide 0.8.0", ] [[package]] -name = "compact_str" -version = "0.8.0" +name = "getrandom" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ - "castaway", "cfg-if", - "itoa", - "rustversion", - "ryu", - "static_assertions", -] - -[[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", + "wasi", ] [[package]] -name = "core-foundation-sys" -version = "0.8.7" +name = "gimli" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] -name = "cpufeatures" -version = "0.2.13" +name = "hashbrown" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" -dependencies = [ - "libc", -] +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] -name = "crc32fast" -version = "1.4.2" +name = "hermit-abi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] -name = "crossbeam-deque" -version = "0.8.5" +name = "humantime" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] -name = "crossbeam-epoch" -version = "0.9.18" +name = "indexmap" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ - "crossbeam-utils", + "equivalent", + "hashbrown", ] [[package]] -name = "crossbeam-utils" -version = "0.8.20" +name = "is_terminal_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] -name = "crossterm" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +name = "krata-xencall" +version = "0.0.21" dependencies = [ - "bitflags 2.6.0", - "crossterm_winapi", + "env_logger", "libc", - "parking_lot", - "winapi", + "log", + "nix", + "thiserror", + "tokio", + "uuid", ] [[package]] -name = "crossterm" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +name = "krata-xenclient" +version = "0.0.21" dependencies = [ - "bitflags 2.6.0", - "crossterm_winapi", - "futures-core", - "mio", - "parking_lot", - "rustix", - "signal-hook", - "signal-hook-mio", - "winapi", + "async-trait", + "env_logger", + "indexmap", + "krata-xencall", + "krata-xenplatform", + "krata-xenstore", + "libc", + "log", + "regex", + "thiserror", + "tokio", + "uuid", ] [[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +name = "krata-xenevtchn" +version = "0.0.21" dependencies = [ - "winapi", + "byteorder", + "libc", + "log", + "nix", + "thiserror", + "tokio", ] [[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +name = "krata-xengnt" +version = "0.0.21" dependencies = [ - "generic-array", - "typenum", + "libc", + "nix", + "thiserror", ] [[package]] -name = "ctrlc" -version = "3.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" +name = "krata-xenplatform" +version = "0.0.21" dependencies = [ - "nix 0.29.0", - "windows-sys 0.59.0", + "async-trait", + "c2rust-bitfields", + "elf", + "env_logger", + "flate2", + "indexmap", + "krata-xencall", + "libc", + "log", + "memchr", + "nix", + "regex", + "slice-copy", + "thiserror", + "tokio", + "uuid", + "xz2", ] [[package]] -name = "darling" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.74", -] - -[[package]] -name = "darling_macro" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "defmt" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99dd22262668b887121d4672af5a64b238f026099f1a2a1b322066c9ecfe9e0" -dependencies = [ - "bitflags 1.3.2", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9f309eff1f79b3ebdf252954d90ae440599c26c2c553fe87a2d17195f2dcb" -dependencies = [ - "defmt-parser", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "defmt-parser" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4a5fefe330e8d7f31b16a318f9ce81000d8e35e69b93eae154d16d2278f70f" -dependencies = [ - "thiserror", -] - -[[package]] -name = "deku" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709ade444d53896e60f6265660eb50480dd08b77bfc822e5dcc233b88b0b2fba" -dependencies = [ - "bitvec", - "deku_derive", - "no_std_io", - "rustversion", -] - -[[package]] -name = "deku_derive" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7534973f93f9de83203e41c8ddd32d230599fa73fa889f3deb1580ccd186913" -dependencies = [ - "darling", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "derive_builder" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" -dependencies = [ - "derive_builder_core", - "syn 2.0.74", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - -[[package]] -name = "elf" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" - -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - -[[package]] -name = "env_filter" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "humantime", - "log", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "etherparse" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21696e6dfe1057a166a042c6d27b89a46aad2ee1003e6e1e03c49d54fd3270d7" -dependencies = [ - "arrayvec", -] - -[[package]] -name = "fancy-duration" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3ae60718ae501dca9d27fd0e322683c86a95a1a01fac1807aa2f9b035cc0882" -dependencies = [ - "anyhow", - "lazy_static", - "regex", -] - -[[package]] -name = "fastrand" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" - -[[package]] -name = "filetime" -version = "0.2.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550" -dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.59.0", -] - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "flate2" -version = "1.0.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" -dependencies = [ - "crc32fast", - "miniz_oxide 0.8.0", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[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 = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futures" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-executor" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - -[[package]] -name = "futures-macro" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "futures-sink" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" - -[[package]] -name = "futures-task" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" - -[[package]] -name = "futures-util" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "getset" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "gimli" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" - -[[package]] -name = "h2" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap 2.4.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hash32" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" -dependencies = [ - "byteorder", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "heapless" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" -dependencies = [ - "hash32", - "stable_deref_trait", -] - -[[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.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "http" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" -dependencies = [ - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "human_bytes" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91f255a4535024abf7640cb288260811fc14794f62b063652ed349f9a6c2348e" - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "hyper" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" -dependencies = [ - "futures-util", - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots", -] - -[[package]] -name = "hyper-timeout" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" -dependencies = [ - "hyper", - "hyper-util", - "pin-project-lite", - "tokio", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "pin-project-lite", - "socket2", - "tokio", - "tower 0.4.13", - "tower-service", - "tracing", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" -dependencies = [ - "equivalent", - "hashbrown 0.14.5", -] - -[[package]] -name = "indicatif" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" -dependencies = [ - "console", - "instant", - "number_prefix", - "portable-atomic", - "unicode-width", -] - -[[package]] -name = "instability" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" -dependencies = [ - "quote", - "syn 2.0.74", -] - -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "ipnet" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" - -[[package]] -name = "ipnetwork" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" -dependencies = [ - "serde", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "jobserver" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" -dependencies = [ - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "krata" -version = "0.0.21" -dependencies = [ - "anyhow", - "async-trait", - "bytes", - "hyper", - "hyper-util", - "libc", - "log", - "nix 0.29.0", - "once_cell", - "pin-project-lite", - "prost", - "prost-build", - "prost-reflect", - "prost-reflect-build", - "prost-types", - "scopeguard", - "serde", - "tokio", - "tokio-stream", - "tonic", - "tonic-build", - "tower 0.5.0", - "url", -] - -[[package]] -name = "krata-advmac" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93156fad4bb9474729fd9f076d7ec2fd573747bc3c3535fd2a085113c071211a" -dependencies = [ - "arrayvec", - "rand", - "serde", -] - -[[package]] -name = "krata-buildtools" -version = "0.0.21" -dependencies = [ - "anyhow", - "env_logger", - "krata-oci", - "krata-tokio-tar", - "oci-spec", - "scopeguard", - "tokio", - "tokio-stream", - "uuid", -] - -[[package]] -name = "krata-ctl" -version = "0.0.21" -dependencies = [ - "anyhow", - "async-stream", - "base64", - "clap", - "comfy-table", - "crossterm 0.28.1", - "ctrlc", - "env_logger", - "fancy-duration", - "human_bytes", - "indicatif", - "krata", - "log", - "prost-reflect", - "prost-types", - "ratatui", - "serde", - "serde_json", - "serde_yaml", - "termtree", - "tokio", - "tokio-stream", - "tonic", - "tower 0.5.0", -] - -[[package]] -name = "krata-daemon" -version = "0.0.21" -dependencies = [ - "anyhow", - "async-stream", - "async-trait", - "bytes", - "circular-buffer", - "clap", - "env_logger", - "futures", - "ipnetwork", - "krata", - "krata-advmac", - "krata-oci", - "krata-runtime", - "krata-tokio-tar", - "log", - "prost", - "redb", - "scopeguard", - "serde", - "serde_json", - "signal-hook", - "tokio", - "tokio-stream", - "toml", - "tonic", - "uuid", -] - -[[package]] -name = "krata-loopdev" -version = "0.0.21" -dependencies = [ - "libc", -] - -[[package]] -name = "krata-network" -version = "0.0.21" -dependencies = [ - "anyhow", - "async-trait", - "bytes", - "clap", - "env_logger", - "etherparse", - "futures", - "krata", - "krata-advmac", - "libc", - "log", - "rtnetlink", - "smoltcp", - "tokio", - "tokio-tun", - "tonic", - "udp-stream", - "uuid", -] - -[[package]] -name = "krata-oci" -version = "0.0.21" -dependencies = [ - "anyhow", - "async-compression", - "async-trait", - "backhand", - "bytes", - "env_logger", - "indexmap 2.4.0", - "krata-tokio-tar", - "log", - "oci-spec", - "path-clean", - "reqwest", - "scopeguard", - "serde", - "serde_json", - "sha256", - "tokio", - "tokio-stream", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "krata-runtime" -version = "0.0.21" -dependencies = [ - "anyhow", - "backhand", - "env_logger", - "indexmap 2.4.0", - "ipnetwork", - "krata", - "krata-advmac", - "krata-loopdev", - "krata-oci", - "krata-xencall", - "krata-xenclient", - "krata-xenevtchn", - "krata-xengnt", - "krata-xenplatform", - "krata-xenstore", - "log", - "serde_json", - "tokio", - "uuid", - "walkdir", -] - -[[package]] -name = "krata-tokio-tar" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba844968838c1c5892da2116e5f744bceab2b43af34539abdd6cd3975eaca973" -dependencies = [ - "filetime", - "futures-core", - "libc", - "redox_syscall 0.3.5", - "tokio", - "tokio-stream", - "xattr", -] - -[[package]] -name = "krata-xencall" -version = "0.0.21" -dependencies = [ - "env_logger", - "libc", - "log", - "nix 0.29.0", - "thiserror", - "tokio", - "uuid", -] - -[[package]] -name = "krata-xenclient" -version = "0.0.21" -dependencies = [ - "async-trait", - "env_logger", - "indexmap 2.4.0", - "krata-xencall", - "krata-xenplatform", - "krata-xenstore", - "libc", - "log", - "regex", - "thiserror", - "tokio", - "uuid", -] - -[[package]] -name = "krata-xenevtchn" -version = "0.0.21" -dependencies = [ - "byteorder", - "libc", - "log", - "nix 0.29.0", - "thiserror", - "tokio", -] - -[[package]] -name = "krata-xengnt" -version = "0.0.21" -dependencies = [ - "libc", - "nix 0.29.0", - "thiserror", -] - -[[package]] -name = "krata-xenplatform" -version = "0.0.21" -dependencies = [ - "async-trait", - "c2rust-bitfields", - "elf", - "env_logger", - "flate2", - "indexmap 2.4.0", - "krata-xencall", - "libc", - "log", - "memchr", - "nix 0.29.0", - "regex", - "slice-copy", - "thiserror", - "tokio", - "uuid", - "xz2", -] - -[[package]] -name = "krata-xenstore" -version = "0.0.21" -dependencies = [ - "byteorder", - "env_logger", - "libc", - "log", - "thiserror", - "tokio", -] - -[[package]] -name = "krata-zone" -version = "0.0.21" -dependencies = [ - "anyhow", - "cgroups-rs", - "env_logger", - "futures", - "ipnetwork", - "krata", - "krata-xenstore", - "libc", - "log", - "nix 0.29.0", - "oci-spec", - "path-absolutize", - "platform-info", - "pty-process", - "rtnetlink", - "serde", - "serde_json", - "sys-mount", - "sysinfo", - "tokio", - "tokio-util", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.158" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.6.0", - "libc", - "redox_syscall 0.5.3", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "lru" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" -dependencies = [ - "hashbrown 0.14.5", -] - -[[package]] -name = "lzma-sys" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - -[[package]] -name = "managed" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" - -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - -[[package]] -name = "miniz_oxide" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" -dependencies = [ - "hermit-abi", - "libc", - "log", - "wasi", - "windows-sys 0.52.0", -] - -[[package]] -name = "multimap" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" - -[[package]] -name = "netlink-packet-core" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" -dependencies = [ - "anyhow", - "byteorder", - "netlink-packet-utils", -] - -[[package]] -name = "netlink-packet-route" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c171cd77b4ee8c7708da746ce392440cb7bcf618d122ec9ecc607b12938bf4" -dependencies = [ - "anyhow", - "byteorder", - "libc", - "log", - "netlink-packet-core", - "netlink-packet-utils", -] - -[[package]] -name = "netlink-packet-utils" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" -dependencies = [ - "anyhow", - "byteorder", - "paste", - "thiserror", -] - -[[package]] -name = "netlink-proto" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b33524dc0968bfad349684447bfce6db937a9ac3332a1fe60c0c5a5ce63f21" -dependencies = [ - "bytes", - "futures", - "log", - "netlink-packet-core", - "netlink-sys", - "thiserror", - "tokio", -] - -[[package]] -name = "netlink-sys" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307" -dependencies = [ - "bytes", - "futures", - "libc", - "log", - "tokio", -] - -[[package]] -name = "nix" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "libc", -] - -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags 2.6.0", - "cfg-if", - "libc", -] - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.6.0", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "no_std_io" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fa5f306a6f2c01b4fd172f29bb46195b1764061bf926c75e96ff55df3178208" -dependencies = [ - "memchr", -] - -[[package]] -name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "number_prefix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" - -[[package]] -name = "object" -version = "0.36.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" -dependencies = [ - "memchr", -] - -[[package]] -name = "oci-spec" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f5a3fe998d50101ae009351fec56d88a69f4ed182e11000e711068c2f5abf72" -dependencies = [ - "derive_builder", - "getset", - "once_cell", - "regex", - "serde", - "serde_json", - "strum", - "strum_macros", - "thiserror", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "ordered-float" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" -dependencies = [ - "num-traits", -] - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.3", - "smallvec", - "windows-targets", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[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-clean" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" - -[[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 = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset", - "indexmap 2.4.0", -] - -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - -[[package]] -name = "platform-info" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5ff316b9c4642feda973c18f0decd6c8b0919d4722566f6e4337cce0dd88217" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "portable-atomic" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" - -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "prettyplease" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" -dependencies = [ - "proc-macro2", - "syn 2.0.74", -] - -[[package]] -name = "proc-macro-crate" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" -dependencies = [ - "toml_edit 0.21.1", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prost" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13db3d3fde688c61e2446b4d843bc27a7e8af269a69440c0308021dc92333cc" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-build" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb182580f71dd070f88d01ce3de9f4da5021db7115d2e1c3605a754153b77c1" -dependencies = [ - "bytes", - "heck", - "itertools", - "log", - "multimap", - "once_cell", - "petgraph", - "prettyplease", - "prost", - "prost-types", - "regex", - "syn 2.0.74", - "tempfile", -] - -[[package]] -name = "prost-derive" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "prost-reflect" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a6a9143ae25c25fa7b6a48d6cc08b10785372060009c25140a4e7c340e95af" -dependencies = [ - "base64", - "once_cell", - "prost", - "prost-reflect-derive", - "prost-types", - "serde", - "serde-value", -] - -[[package]] -name = "prost-reflect-build" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e2537231d94dd2778920c2ada37dd9eb1ac0325bb3ee3ee651bd44c1134123" -dependencies = [ - "prost-build", - "prost-reflect", -] - -[[package]] -name = "prost-reflect-derive" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fce6b22f15cc8d8d400a2b98ad29202b33bd56c7d9ddd815bc803a807ecb65" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "prost-types" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee5168b05f49d4b0ca581206eb14a7b22fafd963efe729ac48eb03266e25cc2" -dependencies = [ - "prost", -] - -[[package]] -name = "pty-process" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8749b545e244c90bf74a5767764cc2194f1888bb42f84015486a64c82bea5cc0" -dependencies = [ - "libc", - "rustix", - "tokio", -] - -[[package]] -name = "quinn" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156" -dependencies = [ - "bytes", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash 2.0.0", - "rustls", - "socket2", - "thiserror", - "tokio", - "tracing", -] - -[[package]] -name = "quinn-proto" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" -dependencies = [ - "bytes", - "rand", - "ring", - "rustc-hash 2.0.0", - "rustls", - "slab", - "thiserror", - "tinyvec", - "tracing", -] - -[[package]] -name = "quinn-udp" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" -dependencies = [ - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "quote" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "ratatui" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" -dependencies = [ - "bitflags 2.6.0", - "cassowary", - "compact_str", - "crossterm 0.28.1", - "instability", - "itertools", - "lru", - "paste", - "strum", - "strum_macros", - "unicode-segmentation", - "unicode-truncate", - "unicode-width", -] - -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redb" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58323dc32ea52a8ae105ff94bc0460c5d906307533ba3401aa63db3cbe491fe5" -dependencies = [ - "libc", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" -dependencies = [ - "bitflags 2.6.0", -] - -[[package]] -name = "regex" -version = "1.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" - -[[package]] -name = "reqwest" -version = "0.12.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" -dependencies = [ - "base64", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pemfile", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 1.0.1", - "tokio", - "tokio-rustls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", - "windows-registry", -] - -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rtnetlink" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b684475344d8df1859ddb2d395dd3dac4f8f3422a1aa0725993cb375fc5caba5" -dependencies = [ - "futures", - "log", - "netlink-packet-core", - "netlink-packet-route", - "netlink-packet-utils", - "netlink-proto", - "netlink-sys", - "nix 0.27.1", - "thiserror", - "tokio", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hash" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" - -[[package]] -name = "rustix" -version = "0.38.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" -dependencies = [ - "bitflags 2.6.0", - "errno", - "itoa", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls" -version = "0.23.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" -dependencies = [ - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pemfile" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" -dependencies = [ - "base64", - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" - -[[package]] -name = "rustls-webpki" -version = "0.102.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serde" -version = "1.0.209" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde-value" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" -dependencies = [ - "ordered-float", - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.209" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "serde_json" -version = "1.0.127" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_spanned" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap 2.4.0", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha256" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" -dependencies = [ - "async-trait", - "bytes", - "hex", - "sha2", - "tokio", -] - -[[package]] -name = "signal-hook" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" -dependencies = [ - "libc", - "mio", - "signal-hook", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "slice-copy" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e00f4662cdca2d22cf4ec4f25c287191de801031d3b08f6dcd85662c10d07a" - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "smart-default" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "smoltcp" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a1a996951e50b5971a2c8c0fa05a381480d70a933064245c4a223ddc87ccc97" -dependencies = [ - "bitflags 1.3.2", - "byteorder", - "cfg-if", - "defmt", - "heapless", - "libc", - "log", - "managed", -] - -[[package]] -name = "socket2" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[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.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.74", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "sync_wrapper" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" -dependencies = [ - "futures-core", -] - -[[package]] -name = "sys-mount" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6acb8bb63826062d5a44b68298cf2e25b84bc151bc0c31c35a83b61f818682a" -dependencies = [ - "bitflags 2.6.0", - "libc", - "smart-default", - "thiserror", - "tracing", -] - -[[package]] -name = "sysinfo" -version = "0.31.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b92e0bdf838cbc1c4c9ba14f9c97a7ec6cdcd1ae66b10e1e42775a25553f45d" -dependencies = [ - "core-foundation-sys", - "libc", - "memchr", - "ntapi", - "rayon", - "windows", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tempfile" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" -dependencies = [ - "cfg-if", - "fastrand", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "termtree" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" - -[[package]] -name = "thiserror" -version = "1.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "tinyvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.39.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" -dependencies = [ - "rustls", - "rustls-pki-types", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-tun" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5381752d5832fc811f89d54fc334951aa435022f494190ba7151661f206df" +name = "krata-xenstore" +version = "0.0.21" dependencies = [ + "byteorder", + "env_logger", "libc", - "nix 0.29.0", - "thiserror", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.20", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap 2.4.0", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" -dependencies = [ - "indexmap 2.4.0", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.6.18", -] - -[[package]] -name = "tonic" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f6ba989e4b2c58ae83d862d3a3e27690b6e3ae630d0deb59f3697f32aa88ad" -dependencies = [ - "async-stream", - "async-trait", - "axum", - "base64", - "bytes", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-timeout", - "hyper-util", - "percent-encoding", - "pin-project", - "prost", - "rustls-pemfile", - "socket2", - "tokio", - "tokio-rustls", - "tokio-stream", - "tower 0.4.13", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tonic-build" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4ee8877250136bd7e3d2331632810a4df4ea5e004656990d8d66d2f5ee8a67" -dependencies = [ - "prettyplease", - "proc-macro2", - "prost-build", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "indexmap 1.9.3", - "pin-project", - "pin-project-lite", - "rand", - "slab", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36b837f86b25d7c0d7988f00a54e74739be6477f2aac6201b8f429a7569991b7" -dependencies = [ - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "udp-stream" -version = "0.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf021324a3dc10f5b46ab1c1cf5635e6a81b5559971967b806674673a5f2a18e" -dependencies = [ - "bytes", "log", + "thiserror", "tokio", ] [[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - -[[package]] -name = "unicode-ident" -version = "1.0.12" +name = "libc" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] -name = "unicode-normalization" -version = "0.1.23" +name = "lock_api" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ - "tinyvec", + "autocfg", + "scopeguard", ] [[package]] -name = "unicode-segmentation" -version = "1.11.0" +name = "log" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] -name = "unicode-truncate" -version = "1.1.0" +name = "lzma-sys" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" dependencies = [ - "itertools", - "unicode-segmentation", - "unicode-width", + "cc", + "libc", + "pkg-config", ] [[package]] -name = "unicode-width" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" - -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" +name = "memchr" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] -name = "untrusted" -version = "0.9.0" +name = "miniz_oxide" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] [[package]] -name = "url" -version = "2.5.2" +name = "miniz_oxide" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", + "adler2", ] [[package]] -name = "utf8parse" -version = "0.2.2" +name = "mio" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys", +] [[package]] -name = "uuid" -version = "1.10.0" +name = "nix" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "getrandom", + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", ] [[package]] -name = "version_check" -version = "0.9.5" +name = "object" +version = "0.36.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +dependencies = [ + "memchr", +] [[package]] -name = "walkdir" -version = "2.5.0" +name = "parking_lot" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ - "same-file", - "winapi-util", + "lock_api", + "parking_lot_core", ] [[package]] -name = "want" -version = "0.3.1" +name = "parking_lot_core" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ - "try-lock", + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", ] [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "pin-project-lite" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] -name = "wasm-bindgen" -version = "0.2.93" +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "proc-macro2" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", + "unicode-ident", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.93" +name = "quote" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ - "bumpalo", - "log", - "once_cell", "proc-macro2", - "quote", - "syn 2.0.74", - "wasm-bindgen-shared", ] [[package]] -name = "wasm-bindgen-futures" -version = "0.4.43" +name = "redox_syscall" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", + "bitflags", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.93" +name = "regex" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ - "quote", - "wasm-bindgen-macro-support", + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.93" +name = "regex-automata" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", - "wasm-bindgen-backend", - "wasm-bindgen-shared", + "aho-corasick", + "memchr", + "regex-syntax", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.93" +name = "regex-syntax" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] -name = "web-sys" -version = "0.3.70" +name = "rustc-demangle" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" -dependencies = [ - "js-sys", - "wasm-bindgen", -] +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] -name = "webpki-roots" -version = "0.26.3" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" -dependencies = [ - "rustls-pki-types", -] +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "winapi" -version = "0.3.9" +name = "signal-hook-registry" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "libc", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "slice-copy" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "f5e00f4662cdca2d22cf4ec4f25c287191de801031d3b08f6dcd85662c10d07a" [[package]] -name = "winapi-util" -version = "0.1.9" +name = "smallvec" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "socket2" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys", +] [[package]] -name = "windows" -version = "0.57.0" +name = "syn" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "windows-core", - "windows-targets", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "windows-core" -version = "0.57.0" +name = "syn" +version = "2.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result 0.1.2", - "windows-targets", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "windows-implement" -version = "0.57.0" +name = "thiserror" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", + "thiserror-impl", ] [[package]] -name = "windows-interface" -version = "0.57.0" +name = "thiserror-impl" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", @@ -3476,58 +634,66 @@ dependencies = [ ] [[package]] -name = "windows-registry" -version = "0.2.0" +name = "tokio" +version = "1.39.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" dependencies = [ - "windows-result 0.2.0", - "windows-strings", - "windows-targets", + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", ] [[package]] -name = "windows-result" -version = "0.1.2" +name = "tokio-macros" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ - "windows-targets", + "proc-macro2", + "quote", + "syn 2.0.74", ] [[package]] -name = "windows-result" -version = "0.2.0" +name = "unicode-ident" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets", -] +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "windows-strings" -version = "0.1.0" +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets", -] +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "windows-sys" -version = "0.52.0" +name = "uuid" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ - "windows-targets", + "getrandom", ] +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] @@ -3596,44 +762,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" -dependencies = [ - "memchr", -] - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "xattr" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" -dependencies = [ - "libc", - "linux-raw-sys", - "rustix", -] - [[package]] name = "xz2" version = "0.1.7" @@ -3642,58 +770,3 @@ checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" dependencies = [ "lzma-sys", ] - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zstd" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml index bc63dc20..e7adf840 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,5 @@ [workspace] members = [ - "crates/build", - "crates/krata", - "crates/oci", - "crates/zone", - "crates/runtime", - "crates/daemon", - "crates/network", - "crates/ctl", "crates/xen/xencall", "crates/xen/xenclient", "crates/xen/xenevtchn", @@ -24,109 +16,30 @@ license = "Apache-2.0" repository = "https://github.com/edera-dev/krata" [workspace.dependencies] -anyhow = "1.0" -arrayvec = "0.7.6" -async-compression = "0.4.12" -async-stream = "0.3.5" async-trait = "0.1.81" -backhand = "0.18.0" -base64 = "0.22.1" byteorder = "1" -bytes = "1.7.1" c2rust-bitfields = "0.18.0" -cgroups-rs = "0.3.4" -circular-buffer = "0.1.7" -comfy-table = "7.1.1" -crossterm = "0.28.1" -ctrlc = "3.4.5" elf = "0.7.4" env_logger = "0.11.5" -etherparse = "0.15.0" -fancy-duration = "0.9.2" flate2 = "1.0" -futures = "0.3.30" -hyper = "1.4.1" -hyper-util = "0.1.7" -human_bytes = "0.4" indexmap = "2.4.0" -indicatif = "0.17.8" -ipnetwork = "0.20.0" libc = "0.2" log = "0.4.22" -loopdev-3 = "0.5.1" -krata-advmac = "1.1.0" -krata-tokio-tar = "0.4.0" memchr = "2" nix = "0.29.0" -oci-spec = "0.6.8" -once_cell = "1.19.0" -path-absolutize = "3.1.1" -path-clean = "1.0.1" -pin-project-lite = "0.2.14" -platform-info = "2.0.3" -prost = "0.13.1" -prost-build = "0.13.1" -prost-reflect-build = "0.14.0" -prost-types = "0.13.1" -pty-process = "0.4.0" -rand = "0.8.5" -ratatui = "0.28.1" -redb = "2.1.2" regex = "1.10.6" -rtnetlink = "0.14.1" -scopeguard = "1.2.0" -serde_json = "1.0.127" -serde_yaml = "0.9" -sha256 = "1.5.0" -signal-hook = "0.3.17" slice-copy = "0.3.0" -smoltcp = "0.11.0" -sysinfo = "0.31.3" -termtree = "0.5.1" thiserror = "1.0" -tokio-tun = "0.11.5" -tokio-util = "0.7.11" -toml = "0.8.19" -tonic-build = "0.12.2" -tower = "0.5.0" -udp-stream = "0.0.12" -url = "2.5.2" -walkdir = "2" xz2 = "0.1" -[workspace.dependencies.clap] -version = "4.5.16" -features = ["derive"] - -[workspace.dependencies.prost-reflect] -version = "0.14.0" -features = ["derive"] - -[workspace.dependencies.reqwest] -version = "0.12.7" -default-features = false -features = ["rustls-tls"] - [workspace.dependencies.serde] version = "1.0.209" features = ["derive"] -[workspace.dependencies.sys-mount] -version = "3.0.0" -default-features = false - [workspace.dependencies.tokio] version = "1.39.3" features = ["full"] -[workspace.dependencies.tokio-stream] -version = "0.1" -features = ["io-util", "net"] - -[workspace.dependencies.tonic] -version = "0.12.2" -features = ["tls"] - [workspace.dependencies.uuid] version = "1.10.0" features = ["v4"] diff --git a/Cross.toml b/Cross.toml deleted file mode 100644 index 0c64139b..00000000 --- a/Cross.toml +++ /dev/null @@ -1,34 +0,0 @@ -[target.aarch64-unknown-linux-gnu] -pre-build = [ - "apt-get update && apt-get --assume-yes install protobuf-compiler" -] - -[target.aarch64-unknown-linux-musl] -pre-build = [ - "apt-get update && apt-get --assume-yes install protobuf-compiler" -] - -[target.x86_64-unknown-linux-gnu] -pre-build = [ - "apt-get update && apt-get --assume-yes install protobuf-compiler" -] - -[target.x86_64-unknown-linux-musl] -pre-build = [ - "apt-get update && apt-get --assume-yes install protobuf-compiler" -] - -[target.riscv64gc-unknown-linux-gnu] -pre-build = [ - "apt-get update && apt-get --assume-yes install protobuf-compiler" -] - -[target.x86_64-unknown-freebsd] -pre-build = [ - "apt-get update && apt-get --assume-yes install protobuf-compiler" -] - -[target.x86_64-unknown-netbsd] -pre-build = [ - "apt-get update && apt-get --assume-yes install protobuf-compiler" -] diff --git a/DEV.md b/DEV.md deleted file mode 100644 index f56c75f0..00000000 --- a/DEV.md +++ /dev/null @@ -1,98 +0,0 @@ -# Development Guide - -## Structure - -krata is composed of four major executables: - -| Executable | Runs On | User Interaction | Dev Runner | Code Path | -|------------|---------|------------------|--------------------------|----------------| -| kratad | host | backend daemon | ./hack/debug/kratad.sh | crates/daemon | -| kratanet | host | backend daemon | ./hack/debug/kratanet.sh | crates/network | -| kratactl | host | CLI tool | ./hack/debug/kratactl.sh | crates/ctl | -| kratazone | zone | none, zone init | N/A | crates/zone | - -You will find the code to each executable available in the bin/ and src/ directories inside -it's corresponding code path from the above table. - -## Environment - -| Component | Specification | Notes | -|--------------|---------------|----------------------------------------------------------------------------------| -| Architecture | x86_64 | aarch64 support is still in development | -| Memory | At least 6GB | dom0 will need to be configured with lower memory limit to give krata zones room | -| Xen | 4.17+ | | -| Debian | stable / sid | Debian is recommended due to the ease of Xen setup | -| rustup | any | Install Rustup from https://rustup.rs | - -## Setup Guide - -1. Install the specified Debian version on a x86_64 host _capable_ of KVM (NOTE: KVM is not used, Xen is a type-1 hypervisor). - -2. Install required packages: - -```sh -$ apt install git xen-system-amd64 build-essential musl-tools \ - protobuf-compiler libprotobuf-dev squashfs-tools erofs-utils -``` - -3. Install [rustup](https://rustup.rs) for managing a Rust environment. - -Make sure to install the targets that you need for krata: - -```sh -$ rustup target add x86_64-unknown-linux-gnu -$ rustup target add x86_64-unknown-linux-musl -``` - -4. Configure `/etc/default/grub.d/xen.cfg` to give krata zones some room: - -```sh -# Configure dom0_mem to be 4GB, but leave the rest of the RAM for krata zones. -GRUB_CMDLINE_XEN_DEFAULT="dom0_mem=4G,max:4G" -``` - -After changing the grub config, update grub: `update-grub` - -Then reboot to boot the system as a Xen dom0. - -You can validate that Xen is setup by running `dmesg | grep "Hypervisor detected"` and ensuring it returns a line like `Hypervisor detected: Xen PV`, if that is missing, the host is not running under Xen. - -5. Clone the krata source code: -```sh -$ git clone https://github.com/edera-dev/krata.git krata -$ cd krata -``` - -6. Fetch the zone kernel image: - -```sh -$ ./hack/kernel/fetch.sh -u -``` - -7. Copy the zone kernel artifacts to `/var/lib/krata/zone/kernel` so it is automatically detected by kratad: - -```sh -$ mkdir -p /var/lib/krata/zone -$ cp target/kernel/kernel-x86_64 /var/lib/krata/zone/kernel -$ cp target/kernel/addons-x86_64.squashfs /var/lib/krata/zone/addons.squashfs -``` - -8. Launch `./hack/debug/kratad.sh` and keep it running in the foreground. -9. Launch `./hack/debug/kratanet.sh` and keep it running in the foreground. -10. Run `kratactl` to launch a zone: - -```sh -$ ./hack/debug/kratactl.sh zone launch --attach alpine:latest -``` - -To detach from the zone console, use `Ctrl + ]` on your keyboard. - -To list the running zones, run: -```sh -$ ./hack/debug/kratactl.sh zone list -``` - -To destroy a running zone, copy it's UUID from either the launch command or the zone list and run: -```sh -$ ./hack/debug/kratactl.sh zone destroy ZONE_UUID -``` diff --git a/FAQ.md b/FAQ.md index 21ba8d67..d7643936 100644 --- a/FAQ.md +++ b/FAQ.md @@ -1,11 +1,5 @@ # Frequently Asked Questions -## How does krata currently work? - -The krata isolation engine makes it possible to launch OCI containers on a Xen hypervisor without utilizing the Xen userspace tooling. krata contains just enough of the userspace of Xen (reimplemented in Rust) to start an x86_64 Xen Linux PV guest, and implements a Linux init process that can boot an OCI container. It does so by converting an OCI image into a squashfs/erofs file and packaging basic startup data in a bundle that the init container can read. - -In addition, due to the desire to reduce dependence on the dom0 network, krata contains a networking daemon called kratanet. kratanet listens for krata guests to startup and launches a userspace networking environment. krata guests can access the dom0 networking stack via the proxynat, which that makes it possible to communicate over UDP, TCP, and ICMP (echo only) to the outside world. In addition, each krata guest is provided a "gateway" IP (both in IPv4 and IPv6) which utilizes smoltcp to provide a virtual host. That virtual host in the future could dial connections into the container to access container networking resources. - ## Why utilize Xen instead of KVM? Xen is a very interesting technology, and Edera believes that type-1 hypervisors are ideal for security. Most OCI isolation techniques use KVM, which is not a type-1 hypervisor, and thus is subject to the security limitations of the OS kernel. A type-1 hypervisor on the other hand provides a minimal attack surface upon which less-trusted guests can be launched on top of. diff --git a/README.md b/README.md index 3da73503..d3fde54e 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,21 @@ # krata -An isolation engine for securing compute workloads. - -```bash -$ kratactl zone launch -a alpine:latest -``` +krata is an implementation of a Xen control-plane in Rust. ![license](https://img.shields.io/github/license/edera-dev/krata) ![discord](https://img.shields.io/discord/1207447453083766814?label=discord) [![check](https://github.com/edera-dev/krata/actions/workflows/check.yml/badge.svg)](https://github.com/edera-dev/krata/actions/workflows/check.yml) -[![nightly](https://github.com/edera-dev/krata/actions/workflows/nightly.yml/badge.svg)](https://github.com/edera-dev/krata/actions/workflows/nightly.yml) --- - [Frequently Asked Questions](FAQ.md) -- [Development Guide](DEV.md) - [Code of Conduct](CODE_OF_CONDUCT.md) - [Security Policy](SECURITY.md) ## Introduction -krata is a single-host workload isolation service. It isolates workloads using a type-1 hypervisor, providing a tight security boundary while preserving performance. - -krata utilizes the core of the Xen hypervisor with a fully memory-safe Rust control plane. +krata is a component of [Edera Protect](https://edera.dev/protect-kubernetes), for secure-by-design infrastructure. +It provides the base layer upon which Edera Protect zones are built on: a securely booted virtualization guest on the Xen hypervisor. ## Hardware Support diff --git a/crates/build/Cargo.toml b/crates/build/Cargo.toml deleted file mode 100644 index 21f6726c..00000000 --- a/crates/build/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "krata-buildtools" -description = "Build tools for krata." -license.workspace = true -version.workspace = true -homepage.workspace = true -repository.workspace = true -edition = "2021" -resolver = "2" -publish = false - -[dependencies] -anyhow = { workspace = true } -env_logger = { workspace = true } -oci-spec = { workspace = true } -scopeguard = { workspace = true } -tokio = { workspace = true } -tokio-stream = { workspace = true } -krata-oci = { path = "../oci", version = "^0.0.21" } -krata-tokio-tar = { workspace = true } -uuid = { workspace = true } - -[[bin]] -name = "build-fetch-kernel" -path = "bin/fetch_kernel.rs" diff --git a/crates/build/bin/fetch_kernel.rs b/crates/build/bin/fetch_kernel.rs deleted file mode 100644 index fa5d332e..00000000 --- a/crates/build/bin/fetch_kernel.rs +++ /dev/null @@ -1,121 +0,0 @@ -use std::{ - env::{self, args}, - path::PathBuf, -}; - -use anyhow::{anyhow, Result}; -use env_logger::Env; -use krataoci::{ - name::ImageName, - packer::{service::OciPackerService, OciPackedFormat}, - progress::OciProgressContext, - registry::OciPlatform, -}; -use oci_spec::image::{Arch, Os}; -use tokio::{ - fs::{self, File}, - io::BufReader, -}; -use tokio_stream::StreamExt; -use tokio_tar::Archive; -use uuid::Uuid; - -#[tokio::main] -async fn main() -> Result<()> { - env_logger::Builder::from_env(Env::default().default_filter_or("warn")).init(); - fs::create_dir_all("target/kernel").await?; - - let arch = env::var("TARGET_ARCH").map_err(|_| anyhow!("missing TARGET_ARCH env var"))?; - println!("kernel architecture: {}", arch); - let platform = OciPlatform::new( - Os::Linux, - match arch.as_str() { - "x86_64" => Arch::Amd64, - "aarch64" => Arch::ARM64, - _ => { - return Err(anyhow!("unknown architecture '{}'", arch)); - } - }, - ); - - let image = ImageName::parse(&args().nth(1).unwrap())?; - let mut cache_dir = env::temp_dir().clone(); - cache_dir.push(format!("krata-cache-{}", Uuid::new_v4())); - fs::create_dir_all(&cache_dir).await?; - - let _delete_cache_dir = scopeguard::guard(cache_dir.clone(), |dir| { - let _ = std::fs::remove_dir_all(dir); - }); - - let (context, _) = OciProgressContext::create(); - let service = OciPackerService::new(None, &cache_dir, platform).await?; - let packed = service - .request(image.clone(), OciPackedFormat::Tar, false, true, context) - .await?; - let annotations = packed - .manifest - .item() - .annotations() - .clone() - .unwrap_or_default(); - let Some(format) = annotations.get("dev.edera.kernel.format") else { - return Err(anyhow!( - "image manifest missing 'dev.edera.kernel.format' annotation" - )); - }; - let Some(version) = annotations.get("dev.edera.kernel.version") else { - return Err(anyhow!( - "image manifest missing 'dev.edera.kernel.version' annotation" - )); - }; - let Some(flavor) = annotations.get("dev.edera.kernel.flavor") else { - return Err(anyhow!( - "image manifest missing 'dev.edera.kernel.flavor' annotation" - )); - }; - - if format != "1" { - return Err(anyhow!("kernel format version '{}' is unknown", format)); - } - - let file = BufReader::new(File::open(packed.path).await?); - let mut archive = Archive::new(file); - let mut entries = archive.entries()?; - - let kernel_image_tar_path = PathBuf::from("kernel/image"); - let kernel_addons_tar_path = PathBuf::from("kernel/addons.squashfs"); - let kernel_image_out_path = PathBuf::from(format!("target/kernel/kernel-{}", arch)); - let kernel_addons_out_path = PathBuf::from(format!("target/kernel/addons-{}.squashfs", arch)); - - if kernel_image_out_path.exists() { - fs::remove_file(&kernel_image_out_path).await?; - } - - if kernel_addons_out_path.exists() { - fs::remove_file(&kernel_addons_out_path).await?; - } - - while let Some(entry) = entries.next().await { - let mut entry = entry?; - let path = entry.path()?.to_path_buf(); - - if !entry.header().entry_type().is_file() { - continue; - } - - if path == kernel_image_tar_path { - entry.unpack(&kernel_image_out_path).await?; - } else if path == kernel_addons_tar_path { - entry.unpack(&kernel_addons_out_path).await?; - } - } - - if !kernel_image_out_path.exists() { - return Err(anyhow!("image did not contain a file named /kernel/image")); - } - - println!("kernel version: v{}", version); - println!("kernel flavor: {}", flavor); - - Ok(()) -} diff --git a/crates/ctl/Cargo.toml b/crates/ctl/Cargo.toml deleted file mode 100644 index e8b3b620..00000000 --- a/crates/ctl/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "krata-ctl" -description = "Command-line tool to control the krata isolation engine" -license.workspace = true -version.workspace = true -homepage.workspace = true -repository.workspace = true -edition = "2021" -resolver = "2" - -[dependencies] -anyhow = { workspace = true } -async-stream = { workspace = true } -base64 = { workspace = true } -clap = { workspace = true } -comfy-table = { workspace = true } -crossterm = { workspace = true, features = ["event-stream"] } -ctrlc = { workspace = true, features = ["termination"] } -env_logger = { workspace = true } -fancy-duration = { workspace = true } -human_bytes = { workspace = true } -indicatif = { workspace = true } -krata = { path = "../krata", version = "^0.0.21" } -log = { workspace = true } -prost-reflect = { workspace = true, features = ["serde"] } -prost-types = { workspace = true } -ratatui = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -serde_yaml = { workspace = true } -termtree = { workspace = true } -tokio = { workspace = true } -tokio-stream = { workspace = true } -tonic = { workspace = true } -tower = { workspace = true } - -[lib] -name = "kratactl" - -[[bin]] -name = "kratactl" -path = "bin/control.rs" diff --git a/crates/ctl/bin/control.rs b/crates/ctl/bin/control.rs deleted file mode 100644 index c5894fc4..00000000 --- a/crates/ctl/bin/control.rs +++ /dev/null @@ -1,11 +0,0 @@ -use anyhow::Result; -use clap::Parser; -use env_logger::Env; - -use kratactl::cli::ControlCommand; - -#[tokio::main] -async fn main() -> Result<()> { - env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); - ControlCommand::parse().run().await -} diff --git a/crates/ctl/src/cli/device/list.rs b/crates/ctl/src/cli/device/list.rs deleted file mode 100644 index ea225e7a..00000000 --- a/crates/ctl/src/cli/device/list.rs +++ /dev/null @@ -1,128 +0,0 @@ -use anyhow::Result; -use clap::{Parser, ValueEnum}; -use comfy_table::{presets::UTF8_FULL_CONDENSED, Cell, Color, Table}; -use krata::{ - events::EventStream, - v1::control::{control_service_client::ControlServiceClient, DeviceInfo, ListDevicesRequest}, -}; - -use serde_json::Value; -use tonic::transport::Channel; - -use crate::format::{kv2line, proto2dynamic, proto2kv}; - -#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] -enum DeviceListFormat { - Table, - Json, - JsonPretty, - Jsonl, - Yaml, - KeyValue, - Simple, -} - -#[derive(Parser)] -#[command(about = "List device information")] -pub struct DeviceListCommand { - #[arg(short, long, default_value = "table", help = "Output format")] - format: DeviceListFormat, -} - -impl DeviceListCommand { - pub async fn run( - self, - mut client: ControlServiceClient, - _events: EventStream, - ) -> Result<()> { - let reply = client - .list_devices(ListDevicesRequest {}) - .await? - .into_inner(); - let mut devices = reply.devices; - - devices.sort_by(|a, b| a.name.cmp(&b.name)); - - match self.format { - DeviceListFormat::Table => { - self.print_devices_table(devices)?; - } - - DeviceListFormat::Simple => { - for device in devices { - println!("{}\t{}\t{}", device.name, device.claimed, device.owner); - } - } - - DeviceListFormat::Json | DeviceListFormat::JsonPretty | DeviceListFormat::Yaml => { - let mut values = Vec::new(); - for device in devices { - let message = proto2dynamic(device)?; - values.push(serde_json::to_value(message)?); - } - let value = Value::Array(values); - let encoded = if self.format == DeviceListFormat::JsonPretty { - serde_json::to_string_pretty(&value)? - } else if self.format == DeviceListFormat::Yaml { - serde_yaml::to_string(&value)? - } else { - serde_json::to_string(&value)? - }; - println!("{}", encoded.trim()); - } - - DeviceListFormat::Jsonl => { - for device in devices { - let message = proto2dynamic(device)?; - println!("{}", serde_json::to_string(&message)?); - } - } - - DeviceListFormat::KeyValue => { - self.print_key_value(devices)?; - } - } - - Ok(()) - } - - fn print_devices_table(&self, devices: Vec) -> Result<()> { - let mut table = Table::new(); - table.load_preset(UTF8_FULL_CONDENSED); - table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic); - table.set_header(vec!["name", "status", "owner"]); - for device in devices { - let status_text = if device.claimed { - "claimed" - } else { - "available" - }; - - let status_color = if device.claimed { - Color::Blue - } else { - Color::Green - }; - - table.add_row(vec![ - Cell::new(device.name), - Cell::new(status_text).fg(status_color), - Cell::new(device.owner), - ]); - } - if table.is_empty() { - println!("no devices configured"); - } else { - println!("{}", table); - } - Ok(()) - } - - fn print_key_value(&self, devices: Vec) -> Result<()> { - for device in devices { - let kvs = proto2kv(device)?; - println!("{}", kv2line(kvs)); - } - Ok(()) - } -} diff --git a/crates/ctl/src/cli/device/mod.rs b/crates/ctl/src/cli/device/mod.rs deleted file mode 100644 index 58d9e2af..00000000 --- a/crates/ctl/src/cli/device/mod.rs +++ /dev/null @@ -1,44 +0,0 @@ -use anyhow::Result; -use clap::{Parser, Subcommand}; -use tonic::transport::Channel; - -use krata::events::EventStream; -use krata::v1::control::control_service_client::ControlServiceClient; - -use crate::cli::device::list::DeviceListCommand; - -pub mod list; - -#[derive(Parser)] -#[command(about = "Manage the devices on the isolation engine")] -pub struct DeviceCommand { - #[command(subcommand)] - subcommand: DeviceCommands, -} - -impl DeviceCommand { - pub async fn run( - self, - client: ControlServiceClient, - events: EventStream, - ) -> Result<()> { - self.subcommand.run(client, events).await - } -} - -#[derive(Subcommand)] -pub enum DeviceCommands { - List(DeviceListCommand), -} - -impl DeviceCommands { - pub async fn run( - self, - client: ControlServiceClient, - events: EventStream, - ) -> Result<()> { - match self { - DeviceCommands::List(list) => list.run(client, events).await, - } - } -} diff --git a/crates/ctl/src/cli/host/cpu_topology.rs b/crates/ctl/src/cli/host/cpu_topology.rs deleted file mode 100644 index 07be264e..00000000 --- a/crates/ctl/src/cli/host/cpu_topology.rs +++ /dev/null @@ -1,104 +0,0 @@ -use anyhow::Result; -use clap::{Parser, ValueEnum}; -use comfy_table::presets::UTF8_FULL_CONDENSED; -use comfy_table::{Cell, Table}; -use krata::v1::control::{ - control_service_client::ControlServiceClient, GetHostCpuTopologyRequest, HostCpuTopologyClass, -}; -use serde_json::Value; - -use crate::format::{kv2line, proto2dynamic, proto2kv}; -use tonic::{transport::Channel, Request}; - -fn class_to_str(input: HostCpuTopologyClass) -> String { - match input { - HostCpuTopologyClass::Standard => "Standard".to_string(), - HostCpuTopologyClass::Performance => "Performance".to_string(), - HostCpuTopologyClass::Efficiency => "Efficiency".to_string(), - } -} - -#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] -enum HostCpuTopologyFormat { - Table, - Json, - JsonPretty, - Jsonl, - Yaml, - KeyValue, -} - -#[derive(Parser)] -#[command(about = "Display information about the host CPU topology")] -pub struct HostCpuTopologyCommand { - #[arg(short, long, default_value = "table", help = "Output format")] - format: HostCpuTopologyFormat, -} - -impl HostCpuTopologyCommand { - pub async fn run(self, mut client: ControlServiceClient) -> Result<()> { - let response = client - .get_host_cpu_topology(Request::new(GetHostCpuTopologyRequest {})) - .await? - .into_inner(); - - match self.format { - HostCpuTopologyFormat::Table => { - let mut table = Table::new(); - table.load_preset(UTF8_FULL_CONDENSED); - table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic); - table.set_header(vec!["id", "node", "socket", "core", "thread", "class"]); - - for (i, cpu) in response.cpus.iter().enumerate() { - table.add_row(vec![ - Cell::new(i), - Cell::new(cpu.node), - Cell::new(cpu.socket), - Cell::new(cpu.core), - Cell::new(cpu.thread), - Cell::new(class_to_str(cpu.class())), - ]); - } - - if !table.is_empty() { - println!("{}", table); - } - } - - HostCpuTopologyFormat::Json - | HostCpuTopologyFormat::JsonPretty - | HostCpuTopologyFormat::Yaml => { - let mut values = Vec::new(); - for cpu in response.cpus { - let message = proto2dynamic(cpu)?; - values.push(serde_json::to_value(message)?); - } - let value = Value::Array(values); - let encoded = if self.format == HostCpuTopologyFormat::JsonPretty { - serde_json::to_string_pretty(&value)? - } else if self.format == HostCpuTopologyFormat::Yaml { - serde_yaml::to_string(&value)? - } else { - serde_json::to_string(&value)? - }; - println!("{}", encoded.trim()); - } - - HostCpuTopologyFormat::Jsonl => { - for cpu in response.cpus { - let message = proto2dynamic(cpu)?; - println!("{}", serde_json::to_string(&message)?); - } - } - - HostCpuTopologyFormat::KeyValue => { - for cpu in response.cpus { - let kvs = proto2kv(cpu)?; - println!("{}", kv2line(kvs),); - } - } - } - - Ok(()) - } -} diff --git a/crates/ctl/src/cli/host/hv_console.rs b/crates/ctl/src/cli/host/hv_console.rs deleted file mode 100644 index c7e044cb..00000000 --- a/crates/ctl/src/cli/host/hv_console.rs +++ /dev/null @@ -1,23 +0,0 @@ -use anyhow::Result; -use clap::Parser; -use krata::v1::control::{ - control_service_client::ControlServiceClient, ReadHypervisorConsoleRequest, -}; - -use tonic::{transport::Channel, Request}; - -#[derive(Parser)] -#[command(about = "Display hypervisor console output")] -pub struct HostHvConsoleCommand {} - -impl HostHvConsoleCommand { - pub async fn run(self, mut client: ControlServiceClient) -> Result<()> { - let response = client - .read_hypervisor_console(Request::new(ReadHypervisorConsoleRequest {})) - .await? - .into_inner(); - - print!("{}", response.data); - Ok(()) - } -} diff --git a/crates/ctl/src/cli/host/idm_snoop.rs b/crates/ctl/src/cli/host/idm_snoop.rs deleted file mode 100644 index e5c0f762..00000000 --- a/crates/ctl/src/cli/host/idm_snoop.rs +++ /dev/null @@ -1,157 +0,0 @@ -use anyhow::Result; -use base64::Engine; -use clap::{Parser, ValueEnum}; -use krata::{ - events::EventStream, - idm::{internal, serialize::IdmSerializable, transport::IdmTransportPacketForm}, - v1::control::{control_service_client::ControlServiceClient, SnoopIdmReply, SnoopIdmRequest}, -}; - -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use tokio_stream::StreamExt; -use tonic::transport::Channel; - -use crate::format::{kv2line, proto2dynamic, value2kv}; - -#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] -enum HostIdmSnoopFormat { - Simple, - Jsonl, - KeyValue, -} - -#[derive(Parser)] -#[command(about = "Snoop on the IDM bus")] -pub struct HostIdmSnoopCommand { - #[arg(short, long, default_value = "simple", help = "Output format")] - format: HostIdmSnoopFormat, -} - -impl HostIdmSnoopCommand { - pub async fn run( - self, - mut client: ControlServiceClient, - _events: EventStream, - ) -> Result<()> { - let mut stream = client.snoop_idm(SnoopIdmRequest {}).await?.into_inner(); - - while let Some(reply) = stream.next().await { - let reply = reply?; - let Some(line) = convert_idm_snoop(reply) else { - continue; - }; - - match self.format { - HostIdmSnoopFormat::Simple => { - self.print_simple(line)?; - } - - HostIdmSnoopFormat::Jsonl => { - let encoded = serde_json::to_string(&line)?; - println!("{}", encoded.trim()); - } - - HostIdmSnoopFormat::KeyValue => { - self.print_key_value(line)?; - } - } - } - - Ok(()) - } - - fn print_simple(&self, line: IdmSnoopLine) -> Result<()> { - let encoded = if !line.packet.decoded.is_null() { - serde_json::to_string(&line.packet.decoded)? - } else { - base64::prelude::BASE64_STANDARD.encode(&line.packet.data) - }; - println!( - "({} -> {}) {} {} {}", - line.from, line.to, line.packet.id, line.packet.form, encoded - ); - Ok(()) - } - - fn print_key_value(&self, line: IdmSnoopLine) -> Result<()> { - let kvs = value2kv(serde_json::to_value(line)?)?; - println!("{}", kv2line(kvs)); - Ok(()) - } -} - -#[derive(Serialize, Deserialize)] -pub struct IdmSnoopLine { - pub from: String, - pub to: String, - pub packet: IdmSnoopData, -} - -#[derive(Serialize, Deserialize)] -pub struct IdmSnoopData { - pub id: u64, - pub channel: u64, - pub form: String, - pub data: String, - pub decoded: Value, -} - -pub fn convert_idm_snoop(reply: SnoopIdmReply) -> Option { - let packet = &(reply.packet?); - - let decoded = if packet.channel == 0 { - match packet.form() { - IdmTransportPacketForm::Event => internal::Event::decode(&packet.data) - .ok() - .and_then(|event| proto2dynamic(event).ok()), - - IdmTransportPacketForm::Request - | IdmTransportPacketForm::StreamRequest - | IdmTransportPacketForm::StreamRequestUpdate => { - internal::Request::decode(&packet.data) - .ok() - .and_then(|event| proto2dynamic(event).ok()) - } - - IdmTransportPacketForm::Response | IdmTransportPacketForm::StreamResponseUpdate => { - internal::Response::decode(&packet.data) - .ok() - .and_then(|event| proto2dynamic(event).ok()) - } - - _ => None, - } - } else { - None - }; - - let decoded = decoded - .and_then(|message| serde_json::to_value(message).ok()) - .unwrap_or(Value::Null); - - let data = IdmSnoopData { - id: packet.id, - channel: packet.channel, - form: match packet.form() { - IdmTransportPacketForm::Raw => "raw".to_string(), - IdmTransportPacketForm::Event => "event".to_string(), - IdmTransportPacketForm::Request => "request".to_string(), - IdmTransportPacketForm::Response => "response".to_string(), - IdmTransportPacketForm::StreamRequest => "stream-request".to_string(), - IdmTransportPacketForm::StreamRequestUpdate => "stream-request-update".to_string(), - IdmTransportPacketForm::StreamRequestClosed => "stream-request-closed".to_string(), - IdmTransportPacketForm::StreamResponseUpdate => "stream-response-update".to_string(), - IdmTransportPacketForm::StreamResponseClosed => "stream-response-closed".to_string(), - _ => format!("unknown-{}", packet.form), - }, - data: base64::prelude::BASE64_STANDARD.encode(&packet.data), - decoded, - }; - - Some(IdmSnoopLine { - from: reply.from, - to: reply.to, - packet: data, - }) -} diff --git a/crates/ctl/src/cli/host/mod.rs b/crates/ctl/src/cli/host/mod.rs deleted file mode 100644 index 399b1dc0..00000000 --- a/crates/ctl/src/cli/host/mod.rs +++ /dev/null @@ -1,59 +0,0 @@ -use anyhow::Result; -use clap::{Parser, Subcommand}; -use tonic::transport::Channel; - -use krata::events::EventStream; -use krata::v1::control::control_service_client::ControlServiceClient; - -use crate::cli::host::cpu_topology::HostCpuTopologyCommand; -use crate::cli::host::hv_console::HostHvConsoleCommand; -use crate::cli::host::idm_snoop::HostIdmSnoopCommand; -use crate::cli::host::status::HostStatusCommand; - -pub mod cpu_topology; -pub mod hv_console; -pub mod idm_snoop; -pub mod status; - -#[derive(Parser)] -#[command(about = "Manage the host of the isolation engine")] -pub struct HostCommand { - #[command(subcommand)] - subcommand: HostCommands, -} - -impl HostCommand { - pub async fn run( - self, - client: ControlServiceClient, - events: EventStream, - ) -> Result<()> { - self.subcommand.run(client, events).await - } -} - -#[derive(Subcommand)] -pub enum HostCommands { - CpuTopology(HostCpuTopologyCommand), - Status(HostStatusCommand), - IdmSnoop(HostIdmSnoopCommand), - HvConsole(HostHvConsoleCommand), -} - -impl HostCommands { - pub async fn run( - self, - client: ControlServiceClient, - events: EventStream, - ) -> Result<()> { - match self { - HostCommands::CpuTopology(cpu_topology) => cpu_topology.run(client).await, - - HostCommands::Status(status) => status.run(client).await, - - HostCommands::IdmSnoop(snoop) => snoop.run(client, events).await, - - HostCommands::HvConsole(hvconsole) => hvconsole.run(client).await, - } - } -} diff --git a/crates/ctl/src/cli/host/status.rs b/crates/ctl/src/cli/host/status.rs deleted file mode 100644 index 858e6103..00000000 --- a/crates/ctl/src/cli/host/status.rs +++ /dev/null @@ -1,60 +0,0 @@ -use anyhow::Result; -use clap::{Parser, ValueEnum}; -use krata::v1::control::{control_service_client::ControlServiceClient, GetHostStatusRequest}; - -use crate::format::{kv2line, proto2dynamic, proto2kv}; -use tonic::{transport::Channel, Request}; - -#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] -enum HostStatusFormat { - Simple, - Json, - JsonPretty, - Yaml, - KeyValue, -} - -#[derive(Parser)] -#[command(about = "Get information about the host")] -pub struct HostStatusCommand { - #[arg(short, long, default_value = "simple", help = "Output format")] - format: HostStatusFormat, -} - -impl HostStatusCommand { - pub async fn run(self, mut client: ControlServiceClient) -> Result<()> { - let response = client - .get_host_status(Request::new(GetHostStatusRequest {})) - .await? - .into_inner(); - match self.format { - HostStatusFormat::Simple => { - println!("Host UUID: {}", response.host_uuid); - println!("Host Domain: {}", response.host_domid); - println!("Krata Version: {}", response.krata_version); - println!("Host IPv4: {}", response.host_ipv4); - println!("Host IPv6: {}", response.host_ipv6); - println!("Host Ethernet Address: {}", response.host_mac); - } - - HostStatusFormat::Json | HostStatusFormat::JsonPretty | HostStatusFormat::Yaml => { - let message = proto2dynamic(response)?; - let value = serde_json::to_value(message)?; - let encoded = if self.format == HostStatusFormat::JsonPretty { - serde_json::to_string_pretty(&value)? - } else if self.format == HostStatusFormat::Yaml { - serde_yaml::to_string(&value)? - } else { - serde_json::to_string(&value)? - }; - println!("{}", encoded.trim()); - } - - HostStatusFormat::KeyValue => { - let kvs = proto2kv(response)?; - println!("{}", kv2line(kvs),); - } - } - Ok(()) - } -} diff --git a/crates/ctl/src/cli/image/mod.rs b/crates/ctl/src/cli/image/mod.rs deleted file mode 100644 index 905fd4e2..00000000 --- a/crates/ctl/src/cli/image/mod.rs +++ /dev/null @@ -1,44 +0,0 @@ -use anyhow::Result; -use clap::{Parser, Subcommand}; -use tonic::transport::Channel; - -use krata::events::EventStream; -use krata::v1::control::control_service_client::ControlServiceClient; - -use crate::cli::image::pull::ImagePullCommand; - -pub mod pull; - -#[derive(Parser)] -#[command(about = "Manage the images on the isolation engine")] -pub struct ImageCommand { - #[command(subcommand)] - subcommand: ImageCommands, -} - -impl ImageCommand { - pub async fn run( - self, - client: ControlServiceClient, - events: EventStream, - ) -> Result<()> { - self.subcommand.run(client, events).await - } -} - -#[derive(Subcommand)] -pub enum ImageCommands { - Pull(ImagePullCommand), -} - -impl ImageCommands { - pub async fn run( - self, - client: ControlServiceClient, - _events: EventStream, - ) -> Result<()> { - match self { - ImageCommands::Pull(pull) => pull.run(client).await, - } - } -} diff --git a/crates/ctl/src/cli/image/pull.rs b/crates/ctl/src/cli/image/pull.rs deleted file mode 100644 index ab2fa617..00000000 --- a/crates/ctl/src/cli/image/pull.rs +++ /dev/null @@ -1,48 +0,0 @@ -use anyhow::Result; -use clap::{Parser, ValueEnum}; -use krata::v1::{ - common::OciImageFormat, - control::{control_service_client::ControlServiceClient, PullImageRequest}, -}; - -use tonic::transport::Channel; - -use crate::pull::pull_interactive_progress; - -#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] -pub enum ImagePullImageFormat { - Squashfs, - Erofs, - Tar, -} - -#[derive(Parser)] -#[command(about = "Pull an image into the cache")] -pub struct ImagePullCommand { - #[arg(help = "Image name")] - image: String, - #[arg(short = 's', long, default_value = "squashfs", help = "Image format")] - image_format: ImagePullImageFormat, - #[arg(short = 'o', long, help = "Overwrite image cache")] - overwrite_cache: bool, -} - -impl ImagePullCommand { - pub async fn run(self, mut client: ControlServiceClient) -> Result<()> { - let response = client - .pull_image(PullImageRequest { - image: self.image.clone(), - format: match self.image_format { - ImagePullImageFormat::Squashfs => OciImageFormat::Squashfs.into(), - ImagePullImageFormat::Erofs => OciImageFormat::Erofs.into(), - ImagePullImageFormat::Tar => OciImageFormat::Tar.into(), - }, - overwrite_cache: self.overwrite_cache, - update: true, - }) - .await?; - let reply = pull_interactive_progress(response.into_inner()).await?; - println!("{}", reply.digest); - Ok(()) - } -} diff --git a/crates/ctl/src/cli/mod.rs b/crates/ctl/src/cli/mod.rs deleted file mode 100644 index 6e3ac8a8..00000000 --- a/crates/ctl/src/cli/mod.rs +++ /dev/null @@ -1,90 +0,0 @@ -pub mod device; -pub mod host; -pub mod image; -pub mod network; -pub mod zone; - -use crate::cli::device::DeviceCommand; -use crate::cli::host::HostCommand; -use crate::cli::image::ImageCommand; -use crate::cli::zone::ZoneCommand; -use anyhow::{anyhow, Result}; -use clap::Parser; -use krata::{ - client::ControlClientProvider, - events::EventStream, - v1::control::{control_service_client::ControlServiceClient, ResolveZoneIdRequest}, -}; -use network::NetworkCommand; -use tonic::{transport::Channel, Request}; - -#[derive(Parser)] -#[command(version, about = "Control the krata isolation engine")] -pub struct ControlCommand { - #[arg( - short, - long, - help = "The connection URL to the krata isolation engine", - default_value = "unix:///var/lib/krata/daemon.socket" - )] - connection: String, - - #[command(subcommand)] - command: ControlCommands, -} - -#[allow(clippy::large_enum_variant)] -#[derive(Parser)] -pub enum ControlCommands { - Zone(ZoneCommand), - Image(ImageCommand), - Network(NetworkCommand), - Device(DeviceCommand), - Host(HostCommand), -} - -impl ControlCommand { - pub async fn run(self) -> Result<()> { - let client = ControlClientProvider::dial(self.connection.parse()?).await?; - let events = EventStream::open(client.clone()).await?; - self.command.run(client, events).await - } -} - -impl ControlCommands { - pub async fn run( - self, - client: ControlServiceClient, - events: EventStream, - ) -> Result<()> { - match self { - ControlCommands::Zone(zone) => zone.run(client, events).await, - - ControlCommands::Network(network) => network.run(client, events).await, - - ControlCommands::Image(image) => image.run(client, events).await, - - ControlCommands::Device(device) => device.run(client, events).await, - - ControlCommands::Host(host) => host.run(client, events).await, - } - } -} - -pub async fn resolve_zone( - client: &mut ControlServiceClient, - name: &str, -) -> Result { - let reply = client - .resolve_zone_id(Request::new(ResolveZoneIdRequest { - name: name.to_string(), - })) - .await? - .into_inner(); - - if !reply.zone_id.is_empty() { - Ok(reply.zone_id) - } else { - Err(anyhow!("unable to resolve zone '{}'", name)) - } -} diff --git a/crates/ctl/src/cli/network/mod.rs b/crates/ctl/src/cli/network/mod.rs deleted file mode 100644 index 5a692b20..00000000 --- a/crates/ctl/src/cli/network/mod.rs +++ /dev/null @@ -1,43 +0,0 @@ -use anyhow::Result; -use clap::{Parser, Subcommand}; -use reservation::NetworkReservationCommand; -use tonic::transport::Channel; - -use krata::events::EventStream; -use krata::v1::control::control_service_client::ControlServiceClient; - -pub mod reservation; - -#[derive(Parser)] -#[command(about = "Manage the network on the isolation engine")] -pub struct NetworkCommand { - #[command(subcommand)] - subcommand: NetworkCommands, -} - -impl NetworkCommand { - pub async fn run( - self, - client: ControlServiceClient, - events: EventStream, - ) -> Result<()> { - self.subcommand.run(client, events).await - } -} - -#[derive(Subcommand)] -pub enum NetworkCommands { - Reservation(NetworkReservationCommand), -} - -impl NetworkCommands { - pub async fn run( - self, - client: ControlServiceClient, - events: EventStream, - ) -> Result<()> { - match self { - NetworkCommands::Reservation(reservation) => reservation.run(client, events).await, - } - } -} diff --git a/crates/ctl/src/cli/network/reservation/list.rs b/crates/ctl/src/cli/network/reservation/list.rs deleted file mode 100644 index eafc9946..00000000 --- a/crates/ctl/src/cli/network/reservation/list.rs +++ /dev/null @@ -1,125 +0,0 @@ -use anyhow::Result; -use clap::{Parser, ValueEnum}; -use comfy_table::{presets::UTF8_FULL_CONDENSED, Cell, Table}; -use krata::{ - events::EventStream, - v1::{ - common::NetworkReservation, - control::{control_service_client::ControlServiceClient, ListNetworkReservationsRequest}, - }, -}; - -use serde_json::Value; -use tonic::transport::Channel; - -use crate::format::{kv2line, proto2dynamic, proto2kv}; - -#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] -enum NetworkReservationListFormat { - Table, - Json, - JsonPretty, - Jsonl, - Yaml, - KeyValue, - Simple, -} - -#[derive(Parser)] -#[command(about = "List network reservation information")] -pub struct NetworkReservationListCommand { - #[arg(short, long, default_value = "table", help = "Output format")] - format: NetworkReservationListFormat, -} - -impl NetworkReservationListCommand { - pub async fn run( - self, - mut client: ControlServiceClient, - _events: EventStream, - ) -> Result<()> { - let reply = client - .list_network_reservations(ListNetworkReservationsRequest {}) - .await? - .into_inner(); - let mut reservations = reply.reservations; - - reservations.sort_by(|a, b| a.uuid.cmp(&b.uuid)); - - match self.format { - NetworkReservationListFormat::Table => { - self.print_reservations_table(reservations)?; - } - - NetworkReservationListFormat::Simple => { - for reservation in reservations { - println!( - "{}\t{}\t{}\t{}", - reservation.uuid, reservation.ipv4, reservation.ipv6, reservation.mac - ); - } - } - - NetworkReservationListFormat::Json - | NetworkReservationListFormat::JsonPretty - | NetworkReservationListFormat::Yaml => { - let mut values = Vec::new(); - for device in reservations { - let message = proto2dynamic(device)?; - values.push(serde_json::to_value(message)?); - } - let value = Value::Array(values); - let encoded = if self.format == NetworkReservationListFormat::JsonPretty { - serde_json::to_string_pretty(&value)? - } else if self.format == NetworkReservationListFormat::Yaml { - serde_yaml::to_string(&value)? - } else { - serde_json::to_string(&value)? - }; - println!("{}", encoded.trim()); - } - - NetworkReservationListFormat::Jsonl => { - for device in reservations { - let message = proto2dynamic(device)?; - println!("{}", serde_json::to_string(&message)?); - } - } - - NetworkReservationListFormat::KeyValue => { - self.print_key_value(reservations)?; - } - } - - Ok(()) - } - - fn print_reservations_table(&self, reservations: Vec) -> Result<()> { - let mut table = Table::new(); - table.load_preset(UTF8_FULL_CONDENSED); - table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic); - table.set_header(vec!["uuid", "ipv4", "ipv6", "mac"]); - for reservation in reservations { - table.add_row(vec![ - Cell::new(reservation.uuid), - Cell::new(reservation.ipv4), - Cell::new(reservation.ipv6), - Cell::new(reservation.mac), - ]); - } - if table.is_empty() { - println!("no network reservations found"); - } else { - println!("{}", table); - } - Ok(()) - } - - fn print_key_value(&self, reservations: Vec) -> Result<()> { - for reservation in reservations { - let kvs = proto2kv(reservation)?; - println!("{}", kv2line(kvs)); - } - Ok(()) - } -} diff --git a/crates/ctl/src/cli/network/reservation/mod.rs b/crates/ctl/src/cli/network/reservation/mod.rs deleted file mode 100644 index ef13a9b8..00000000 --- a/crates/ctl/src/cli/network/reservation/mod.rs +++ /dev/null @@ -1,43 +0,0 @@ -use anyhow::Result; -use clap::{Parser, Subcommand}; -use list::NetworkReservationListCommand; -use tonic::transport::Channel; - -use krata::events::EventStream; -use krata::v1::control::control_service_client::ControlServiceClient; - -pub mod list; - -#[derive(Parser)] -#[command(about = "Manage network reservations")] -pub struct NetworkReservationCommand { - #[command(subcommand)] - subcommand: NetworkReservationCommands, -} - -impl NetworkReservationCommand { - pub async fn run( - self, - client: ControlServiceClient, - events: EventStream, - ) -> Result<()> { - self.subcommand.run(client, events).await - } -} - -#[derive(Subcommand)] -pub enum NetworkReservationCommands { - List(NetworkReservationListCommand), -} - -impl NetworkReservationCommands { - pub async fn run( - self, - client: ControlServiceClient, - events: EventStream, - ) -> Result<()> { - match self { - NetworkReservationCommands::List(list) => list.run(client, events).await, - } - } -} diff --git a/crates/ctl/src/cli/zone/attach.rs b/crates/ctl/src/cli/zone/attach.rs deleted file mode 100644 index 6742d2dc..00000000 --- a/crates/ctl/src/cli/zone/attach.rs +++ /dev/null @@ -1,41 +0,0 @@ -use anyhow::Result; -use clap::Parser; -use krata::{events::EventStream, v1::control::control_service_client::ControlServiceClient}; - -use tokio::select; -use tonic::transport::Channel; - -use crate::console::StdioConsoleStream; - -use crate::cli::resolve_zone; - -#[derive(Parser)] -#[command(about = "Attach to the zone console")] -pub struct ZoneAttachCommand { - #[arg(help = "Zone to attach to, either the name or the uuid")] - zone: String, -} - -impl ZoneAttachCommand { - pub async fn run( - self, - mut client: ControlServiceClient, - events: EventStream, - ) -> Result<()> { - let zone_id: String = resolve_zone(&mut client, &self.zone).await?; - let input = StdioConsoleStream::stdin_stream(zone_id.clone(), false).await; - let output = client.attach_zone_console(input).await?.into_inner(); - let stdout_handle = - tokio::task::spawn(async move { StdioConsoleStream::stdout(output, true).await }); - let exit_hook_task = StdioConsoleStream::zone_exit_hook(zone_id.clone(), events).await?; - let code = select! { - x = stdout_handle => { - x??; - None - }, - x = exit_hook_task => x? - }; - StdioConsoleStream::restore_terminal_mode(); - std::process::exit(code.unwrap_or(0)); - } -} diff --git a/crates/ctl/src/cli/zone/destroy.rs b/crates/ctl/src/cli/zone/destroy.rs deleted file mode 100644 index 543618a0..00000000 --- a/crates/ctl/src/cli/zone/destroy.rs +++ /dev/null @@ -1,78 +0,0 @@ -use anyhow::Result; -use clap::Parser; -use krata::{ - events::EventStream, - v1::control::{ - control_service_client::ControlServiceClient, watch_events_reply::Event, DestroyZoneRequest, - }, -}; - -use crate::cli::resolve_zone; -use krata::v1::common::ZoneState; -use log::error; -use tonic::{transport::Channel, Request}; - -#[derive(Parser)] -#[command(about = "Destroy a zone")] -pub struct ZoneDestroyCommand { - #[arg( - short = 'W', - long, - help = "Wait for the destruction of the zone to complete" - )] - wait: bool, - #[arg(help = "Zone to destroy, either the name or the uuid")] - zone: String, -} - -impl ZoneDestroyCommand { - pub async fn run( - self, - mut client: ControlServiceClient, - events: EventStream, - ) -> Result<()> { - let zone_id: String = resolve_zone(&mut client, &self.zone).await?; - let _ = client - .destroy_zone(Request::new(DestroyZoneRequest { - zone_id: zone_id.clone(), - })) - .await? - .into_inner(); - if self.wait { - wait_zone_destroyed(&zone_id, events).await?; - } - Ok(()) - } -} - -async fn wait_zone_destroyed(id: &str, events: EventStream) -> Result<()> { - let mut stream = events.subscribe(); - while let Ok(event) = stream.recv().await { - let Event::ZoneChanged(changed) = event; - let Some(zone) = changed.zone else { - continue; - }; - - if zone.id != id { - continue; - } - - let Some(status) = zone.status else { - continue; - }; - - if let Some(ref error) = status.error_status { - if status.state() == ZoneState::Failed { - error!("destroy failed: {}", error.message); - std::process::exit(1); - } else { - error!("zone error: {}", error.message); - } - } - - if status.state() == ZoneState::Destroyed { - std::process::exit(0); - } - } - Ok(()) -} diff --git a/crates/ctl/src/cli/zone/exec.rs b/crates/ctl/src/cli/zone/exec.rs deleted file mode 100644 index ef2e247e..00000000 --- a/crates/ctl/src/cli/zone/exec.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::collections::HashMap; - -use anyhow::Result; - -use clap::Parser; -use crossterm::tty::IsTty; -use krata::v1::{ - common::{TerminalSize, ZoneTaskSpec, ZoneTaskSpecEnvVar}, - control::{control_service_client::ControlServiceClient, ExecInsideZoneRequest}, -}; - -use tokio::io::stdin; -use tonic::{transport::Channel, Request}; - -use crate::console::StdioConsoleStream; - -use crate::cli::resolve_zone; - -#[derive(Parser)] -#[command(about = "Execute a command inside the zone")] -pub struct ZoneExecCommand { - #[arg[short, long, help = "Environment variables"]] - env: Option>, - #[arg(short = 'w', long, help = "Working directory")] - working_directory: Option, - #[arg(short = 't', long, help = "Allocate tty")] - tty: bool, - #[arg(help = "Zone to exec inside, either the name or the uuid")] - zone: String, - #[arg( - allow_hyphen_values = true, - trailing_var_arg = true, - help = "Command to run inside the zone" - )] - command: Vec, -} - -impl ZoneExecCommand { - pub async fn run(self, mut client: ControlServiceClient) -> Result<()> { - let zone_id: String = resolve_zone(&mut client, &self.zone).await?; - let should_map_tty = self.tty && stdin().is_tty(); - let initial = ExecInsideZoneRequest { - zone_id, - task: Some(ZoneTaskSpec { - environment: env_map(&self.env.unwrap_or_default()) - .iter() - .map(|(key, value)| ZoneTaskSpecEnvVar { - key: key.clone(), - value: value.clone(), - }) - .collect(), - command: self.command, - working_directory: self.working_directory.unwrap_or_default(), - tty: self.tty, - }), - stdin: vec![], - stdin_closed: false, - terminal_size: if should_map_tty { - let size = crossterm::terminal::size().ok(); - size.map(|(columns, rows)| TerminalSize { - rows: rows as u32, - columns: columns as u32, - }) - } else { - None - }, - }; - - let stream = StdioConsoleStream::input_stream_exec(initial, should_map_tty).await; - - let response = client - .exec_inside_zone(Request::new(stream)) - .await? - .into_inner(); - - let code = StdioConsoleStream::exec_output(response, should_map_tty).await?; - std::process::exit(code); - } -} - -fn env_map(env: &[String]) -> HashMap { - let mut map = HashMap::::new(); - for item in env { - if let Some((key, value)) = item.split_once('=') { - map.insert(key.to_string(), value.to_string()); - } - } - map -} diff --git a/crates/ctl/src/cli/zone/launch.rs b/crates/ctl/src/cli/zone/launch.rs deleted file mode 100644 index 7fea602c..00000000 --- a/crates/ctl/src/cli/zone/launch.rs +++ /dev/null @@ -1,282 +0,0 @@ -use std::collections::HashMap; - -use anyhow::Result; -use clap::{Parser, ValueEnum}; -use krata::{ - events::EventStream, - v1::{ - common::{ - zone_image_spec::Image, OciImageFormat, ZoneImageSpec, ZoneKernelOptionsSpec, - ZoneOciImageSpec, ZoneResourceSpec, ZoneSpec, ZoneSpecDevice, ZoneState, ZoneTaskSpec, - ZoneTaskSpecEnvVar, - }, - control::{ - control_service_client::ControlServiceClient, watch_events_reply::Event, - CreateZoneRequest, PullImageRequest, - }, - }, -}; -use log::error; -use tokio::select; -use tonic::{transport::Channel, Request}; - -use crate::{console::StdioConsoleStream, pull::pull_interactive_progress}; - -#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] -pub enum LaunchImageFormat { - Squashfs, - Erofs, -} - -#[derive(Parser)] -#[command(about = "Launch a new zone")] -pub struct ZoneLaunchCommand { - #[arg(long, default_value = "squashfs", help = "Image format")] - image_format: LaunchImageFormat, - #[arg(long, help = "Overwrite image cache on pull")] - pull_overwrite_cache: bool, - #[arg(long, help = "Update image on pull")] - pull_update: bool, - #[arg(short, long, help = "Name of the zone")] - name: Option, - #[arg( - short = 'C', - long = "max-cpus", - default_value_t = 4, - help = "Maximum vCPUs available for the zone" - )] - max_cpus: u32, - #[arg( - short = 'c', - long = "target-cpus", - default_value_t = 1, - help = "Target vCPUs for the zone to use" - )] - target_cpus: u32, - #[arg( - short = 'M', - long = "max-memory", - default_value_t = 1024, - help = "Maximum memory available to the zone, in megabytes" - )] - max_memory: u64, - #[arg( - short = 'm', - long = "target-memory", - default_value_t = 1024, - help = "Target memory for the zone to use, in megabytes" - )] - target_memory: u64, - #[arg[short = 'D', long = "device", help = "Devices to request for the zone"]] - device: Vec, - #[arg[short, long, help = "Environment variables set in the zone"]] - env: Option>, - #[arg(short = 't', long, help = "Allocate tty for task")] - tty: bool, - #[arg( - short, - long, - help = "Attach to the zone after zone starts, implies --wait" - )] - attach: bool, - #[arg( - short = 'W', - long, - help = "Wait for the zone to start, implied by --attach" - )] - wait: bool, - #[arg(short = 'k', long, help = "OCI kernel image for zone to use")] - kernel: Option, - #[arg(short = 'I', long, help = "OCI initrd image for zone to use")] - initrd: Option, - #[arg(short = 'w', long, help = "Working directory")] - working_directory: Option, - #[arg(long, help = "Enable verbose logging on the kernel")] - kernel_verbose: bool, - #[arg(long, help = "Additional kernel cmdline options")] - kernel_cmdline_append: Option, - #[arg(help = "Container image for zone to use")] - oci: String, - #[arg( - allow_hyphen_values = true, - trailing_var_arg = true, - help = "Command to run inside the zone" - )] - command: Vec, -} - -impl ZoneLaunchCommand { - pub async fn run( - self, - mut client: ControlServiceClient, - events: EventStream, - ) -> Result<()> { - let image = self - .pull_image( - &mut client, - &self.oci, - match self.image_format { - LaunchImageFormat::Squashfs => OciImageFormat::Squashfs, - LaunchImageFormat::Erofs => OciImageFormat::Erofs, - }, - ) - .await?; - - let kernel = if let Some(ref kernel) = self.kernel { - let kernel_image = self - .pull_image(&mut client, kernel, OciImageFormat::Tar) - .await?; - Some(kernel_image) - } else { - None - }; - - let initrd = if let Some(ref initrd) = self.initrd { - let kernel_image = self - .pull_image(&mut client, initrd, OciImageFormat::Tar) - .await?; - Some(kernel_image) - } else { - None - }; - - let request = CreateZoneRequest { - spec: Some(ZoneSpec { - name: self.name.unwrap_or_default(), - image: Some(image), - kernel, - initrd, - initial_resources: Some(ZoneResourceSpec { - max_memory: self.max_memory, - target_memory: self.target_memory, - max_cpus: self.max_cpus, - target_cpus: self.target_cpus, - }), - task: Some(ZoneTaskSpec { - environment: env_map(&self.env.unwrap_or_default()) - .iter() - .map(|(key, value)| ZoneTaskSpecEnvVar { - key: key.clone(), - value: value.clone(), - }) - .collect(), - command: self.command, - working_directory: self.working_directory.unwrap_or_default(), - tty: self.tty, - }), - annotations: vec![], - devices: self - .device - .iter() - .map(|name| ZoneSpecDevice { name: name.clone() }) - .collect(), - kernel_options: Some(ZoneKernelOptionsSpec { - verbose: self.kernel_verbose, - cmdline_append: self.kernel_cmdline_append.clone().unwrap_or_default(), - }), - }), - }; - let response = client - .create_zone(Request::new(request)) - .await? - .into_inner(); - let id = response.zone_id; - - if self.wait || self.attach { - wait_zone_started(&id, events.clone()).await?; - } - - let code = if self.attach { - let input = StdioConsoleStream::stdin_stream(id.clone(), true).await; - let output = client.attach_zone_console(input).await?.into_inner(); - let stdout_handle = - tokio::task::spawn(async move { StdioConsoleStream::stdout(output, true).await }); - let exit_hook_task = StdioConsoleStream::zone_exit_hook(id.clone(), events).await?; - select! { - x = stdout_handle => { - x??; - None - }, - x = exit_hook_task => x? - } - } else { - println!("{}", id); - None - }; - StdioConsoleStream::restore_terminal_mode(); - std::process::exit(code.unwrap_or(0)); - } - - async fn pull_image( - &self, - client: &mut ControlServiceClient, - image: &str, - format: OciImageFormat, - ) -> Result { - let response = client - .pull_image(PullImageRequest { - image: image.to_string(), - format: format.into(), - overwrite_cache: self.pull_overwrite_cache, - update: self.pull_update, - }) - .await?; - let reply = pull_interactive_progress(response.into_inner()).await?; - Ok(ZoneImageSpec { - image: Some(Image::Oci(ZoneOciImageSpec { - digest: reply.digest, - format: reply.format, - })), - }) - } -} - -async fn wait_zone_started(id: &str, events: EventStream) -> Result<()> { - let mut stream = events.subscribe(); - while let Ok(event) = stream.recv().await { - match event { - Event::ZoneChanged(changed) => { - let Some(zone) = changed.zone else { - continue; - }; - - if zone.id != id { - continue; - } - - let Some(status) = zone.status else { - continue; - }; - - if let Some(ref error) = status.error_status { - if status.state() == ZoneState::Failed { - error!("launch failed: {}", error.message); - std::process::exit(1); - } else { - error!("zone error: {}", error.message); - } - } - - if status.state() == ZoneState::Destroyed { - error!("zone destroyed"); - std::process::exit(1); - } - - if status.state() == ZoneState::Created { - break; - } - } - } - } - Ok(()) -} - -fn env_map(env: &[String]) -> HashMap { - let mut map = HashMap::::new(); - for item in env { - if let Some((key, value)) = item.split_once('=') { - map.insert(key.to_string(), value.to_string()); - } - } - map -} diff --git a/crates/ctl/src/cli/zone/list.rs b/crates/ctl/src/cli/zone/list.rs deleted file mode 100644 index 40b91e33..00000000 --- a/crates/ctl/src/cli/zone/list.rs +++ /dev/null @@ -1,181 +0,0 @@ -use anyhow::{anyhow, Result}; -use clap::{Parser, ValueEnum}; -use comfy_table::{presets::UTF8_FULL_CONDENSED, Cell, Color, Table}; -use krata::{ - events::EventStream, - v1::{ - common::Zone, - control::{ - control_service_client::ControlServiceClient, ListZonesRequest, ResolveZoneIdRequest, - }, - }, -}; - -use crate::format::{kv2line, proto2dynamic, proto2kv, zone_simple_line, zone_state_text}; -use krata::v1::common::ZoneState; -use krata::v1::control::GetZoneRequest; -use serde_json::Value; -use tonic::{transport::Channel, Request}; - -#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] -enum ZoneListFormat { - Table, - Json, - JsonPretty, - Jsonl, - Yaml, - KeyValue, - Simple, -} - -#[derive(Parser)] -#[command(about = "List zone information")] -pub struct ZoneListCommand { - #[arg(short, long, default_value = "table", help = "Output format")] - format: ZoneListFormat, - #[arg(help = "Limit to a single zone, either the name or the uuid")] - zone: Option, -} - -impl ZoneListCommand { - pub async fn run( - self, - mut client: ControlServiceClient, - _events: EventStream, - ) -> Result<()> { - let mut zones = if let Some(ref zone) = self.zone { - let reply = client - .resolve_zone_id(Request::new(ResolveZoneIdRequest { name: zone.clone() })) - .await? - .into_inner(); - if !reply.zone_id.is_empty() { - let reply = client - .get_zone(Request::new(GetZoneRequest { - zone_id: reply.zone_id, - })) - .await? - .into_inner(); - if let Some(zone) = reply.zone { - vec![zone] - } else { - return Err(anyhow!("unable to resolve zone '{}'", zone)); - } - } else { - return Err(anyhow!("unable to resolve zone '{}'", zone)); - } - } else { - client - .list_zones(Request::new(ListZonesRequest {})) - .await? - .into_inner() - .zones - }; - - zones.sort_by(|a, b| { - a.spec - .as_ref() - .map(|x| x.name.as_str()) - .unwrap_or("") - .cmp(b.spec.as_ref().map(|x| x.name.as_str()).unwrap_or("")) - }); - - match self.format { - ZoneListFormat::Table => { - self.print_zone_table(zones)?; - } - - ZoneListFormat::Simple => { - for zone in zones { - println!("{}", zone_simple_line(&zone)); - } - } - - ZoneListFormat::Json | ZoneListFormat::JsonPretty | ZoneListFormat::Yaml => { - let mut values = Vec::new(); - for zone in zones { - let message = proto2dynamic(zone)?; - values.push(serde_json::to_value(message)?); - } - let value = Value::Array(values); - let encoded = if self.format == ZoneListFormat::JsonPretty { - serde_json::to_string_pretty(&value)? - } else if self.format == ZoneListFormat::Yaml { - serde_yaml::to_string(&value)? - } else { - serde_json::to_string(&value)? - }; - println!("{}", encoded.trim()); - } - - ZoneListFormat::Jsonl => { - for zone in zones { - let message = proto2dynamic(zone)?; - println!("{}", serde_json::to_string(&message)?); - } - } - - ZoneListFormat::KeyValue => { - self.print_key_value(zones)?; - } - } - - Ok(()) - } - - fn print_zone_table(&self, zones: Vec) -> Result<()> { - let mut table = Table::new(); - table.load_preset(UTF8_FULL_CONDENSED); - table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic); - table.set_header(vec!["name", "uuid", "state", "ipv4", "ipv6"]); - for zone in zones { - let ipv4 = zone - .status - .as_ref() - .and_then(|x| x.network_status.as_ref()) - .map(|x| x.zone_ipv4.as_str()) - .unwrap_or("n/a"); - let ipv6 = zone - .status - .as_ref() - .and_then(|x| x.network_status.as_ref()) - .map(|x| x.zone_ipv6.as_str()) - .unwrap_or("n/a"); - let Some(spec) = zone.spec else { - continue; - }; - let state = zone.status.as_ref().cloned().unwrap_or_default().state(); - let status_text = zone_state_text(state); - - let status_color = match state { - ZoneState::Destroyed | ZoneState::Failed => Color::Red, - ZoneState::Destroying | ZoneState::Exited | ZoneState::Creating => Color::Yellow, - ZoneState::Created => Color::Green, - _ => Color::Reset, - }; - - table.add_row(vec![ - Cell::new(spec.name), - Cell::new(zone.id), - Cell::new(status_text).fg(status_color), - Cell::new(ipv4.to_string()), - Cell::new(ipv6.to_string()), - ]); - } - if table.is_empty() { - if self.zone.is_none() { - println!("no zones have been launched"); - } - } else { - println!("{}", table); - } - Ok(()) - } - - fn print_key_value(&self, zones: Vec) -> Result<()> { - for zone in zones { - let kvs = proto2kv(zone)?; - println!("{}", kv2line(kvs),); - } - Ok(()) - } -} diff --git a/crates/ctl/src/cli/zone/logs.rs b/crates/ctl/src/cli/zone/logs.rs deleted file mode 100644 index 6beacab8..00000000 --- a/crates/ctl/src/cli/zone/logs.rs +++ /dev/null @@ -1,58 +0,0 @@ -use anyhow::Result; -use async_stream::stream; -use clap::Parser; -use krata::{ - events::EventStream, - v1::control::{control_service_client::ControlServiceClient, ZoneConsoleRequest}, -}; - -use tokio::select; -use tokio_stream::{pending, StreamExt}; -use tonic::transport::Channel; - -use crate::console::StdioConsoleStream; - -use crate::cli::resolve_zone; - -#[derive(Parser)] -#[command(about = "View the logs of a zone")] -pub struct ZoneLogsCommand { - #[arg(short, long, help = "Follow output from the zone")] - follow: bool, - #[arg(help = "Zone to show logs for, either the name or the uuid")] - zone: String, -} - -impl ZoneLogsCommand { - pub async fn run( - self, - mut client: ControlServiceClient, - events: EventStream, - ) -> Result<()> { - let zone_id: String = resolve_zone(&mut client, &self.zone).await?; - let zone_id_stream = zone_id.clone(); - let follow = self.follow; - let input = stream! { - yield ZoneConsoleRequest { zone_id: zone_id_stream, replay_history: true, data: Vec::new() }; - if follow { - let mut pending = pending::(); - while let Some(x) = pending.next().await { - yield x; - } - } - }; - let output = client.attach_zone_console(input).await?.into_inner(); - let stdout_handle = - tokio::task::spawn(async move { StdioConsoleStream::stdout(output, false).await }); - let exit_hook_task = StdioConsoleStream::zone_exit_hook(zone_id.clone(), events).await?; - let code = select! { - x = stdout_handle => { - x??; - None - }, - x = exit_hook_task => x? - }; - StdioConsoleStream::restore_terminal_mode(); - std::process::exit(code.unwrap_or(0)); - } -} diff --git a/crates/ctl/src/cli/zone/metrics.rs b/crates/ctl/src/cli/zone/metrics.rs deleted file mode 100644 index e97eedd8..00000000 --- a/crates/ctl/src/cli/zone/metrics.rs +++ /dev/null @@ -1,83 +0,0 @@ -use anyhow::Result; -use clap::{Parser, ValueEnum}; -use krata::{ - events::EventStream, - v1::{ - common::ZoneMetricNode, - control::{control_service_client::ControlServiceClient, ReadZoneMetricsRequest}, - }, -}; - -use tonic::transport::Channel; - -use crate::format::{kv2line, metrics_flat, metrics_tree, proto2dynamic}; - -use crate::cli::resolve_zone; - -#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] -enum ZoneMetricsFormat { - Tree, - Json, - JsonPretty, - Yaml, - KeyValue, -} - -#[derive(Parser)] -#[command(about = "Read metrics from the zone")] -pub struct ZoneMetricsCommand { - #[arg(short, long, default_value = "tree", help = "Output format")] - format: ZoneMetricsFormat, - #[arg(help = "Zone to read metrics for, either the name or the uuid")] - zone: String, -} - -impl ZoneMetricsCommand { - pub async fn run( - self, - mut client: ControlServiceClient, - _events: EventStream, - ) -> Result<()> { - let zone_id: String = resolve_zone(&mut client, &self.zone).await?; - let root = client - .read_zone_metrics(ReadZoneMetricsRequest { zone_id }) - .await? - .into_inner() - .root - .unwrap_or_default(); - match self.format { - ZoneMetricsFormat::Tree => { - self.print_metrics_tree(root)?; - } - - ZoneMetricsFormat::Json | ZoneMetricsFormat::JsonPretty | ZoneMetricsFormat::Yaml => { - let value = serde_json::to_value(proto2dynamic(root)?)?; - let encoded = if self.format == ZoneMetricsFormat::JsonPretty { - serde_json::to_string_pretty(&value)? - } else if self.format == ZoneMetricsFormat::Yaml { - serde_yaml::to_string(&value)? - } else { - serde_json::to_string(&value)? - }; - println!("{}", encoded.trim()); - } - - ZoneMetricsFormat::KeyValue => { - self.print_key_value(root)?; - } - } - - Ok(()) - } - - fn print_metrics_tree(&self, root: ZoneMetricNode) -> Result<()> { - print!("{}", metrics_tree(root)); - Ok(()) - } - - fn print_key_value(&self, metrics: ZoneMetricNode) -> Result<()> { - let kvs = metrics_flat(metrics); - println!("{}", kv2line(kvs)); - Ok(()) - } -} diff --git a/crates/ctl/src/cli/zone/mod.rs b/crates/ctl/src/cli/zone/mod.rs deleted file mode 100644 index e9f1c381..00000000 --- a/crates/ctl/src/cli/zone/mod.rs +++ /dev/null @@ -1,95 +0,0 @@ -use anyhow::Result; -use clap::{Parser, Subcommand}; -use tonic::transport::Channel; - -use krata::events::EventStream; -use krata::v1::control::control_service_client::ControlServiceClient; - -use crate::cli::zone::attach::ZoneAttachCommand; -use crate::cli::zone::destroy::ZoneDestroyCommand; -use crate::cli::zone::exec::ZoneExecCommand; -use crate::cli::zone::launch::ZoneLaunchCommand; -use crate::cli::zone::list::ZoneListCommand; -use crate::cli::zone::logs::ZoneLogsCommand; -use crate::cli::zone::metrics::ZoneMetricsCommand; -use crate::cli::zone::resolve::ZoneResolveCommand; -use crate::cli::zone::top::ZoneTopCommand; -use crate::cli::zone::update_resources::ZoneUpdateResourcesCommand; -use crate::cli::zone::watch::ZoneWatchCommand; - -pub mod attach; -pub mod destroy; -pub mod exec; -pub mod launch; -pub mod list; -pub mod logs; -pub mod metrics; -pub mod resolve; -pub mod top; -pub mod update_resources; -pub mod watch; - -#[derive(Parser)] -#[command(about = "Manage the zones on the isolation engine")] -pub struct ZoneCommand { - #[command(subcommand)] - subcommand: ZoneCommands, -} - -impl ZoneCommand { - pub async fn run( - self, - client: ControlServiceClient, - events: EventStream, - ) -> Result<()> { - self.subcommand.run(client, events).await - } -} - -#[allow(clippy::large_enum_variant)] -#[derive(Subcommand)] -pub enum ZoneCommands { - Attach(ZoneAttachCommand), - List(ZoneListCommand), - Launch(ZoneLaunchCommand), - Destroy(ZoneDestroyCommand), - Exec(ZoneExecCommand), - Logs(ZoneLogsCommand), - Metrics(ZoneMetricsCommand), - Resolve(ZoneResolveCommand), - Top(ZoneTopCommand), - Watch(ZoneWatchCommand), - UpdateResources(ZoneUpdateResourcesCommand), -} - -impl ZoneCommands { - pub async fn run( - self, - client: ControlServiceClient, - events: EventStream, - ) -> Result<()> { - match self { - ZoneCommands::Launch(launch) => launch.run(client, events).await, - - ZoneCommands::Destroy(destroy) => destroy.run(client, events).await, - - ZoneCommands::Attach(attach) => attach.run(client, events).await, - - ZoneCommands::Logs(logs) => logs.run(client, events).await, - - ZoneCommands::List(list) => list.run(client, events).await, - - ZoneCommands::Watch(watch) => watch.run(events).await, - - ZoneCommands::Resolve(resolve) => resolve.run(client).await, - - ZoneCommands::Metrics(metrics) => metrics.run(client, events).await, - - ZoneCommands::Top(top) => top.run(client, events).await, - - ZoneCommands::Exec(exec) => exec.run(client).await, - - ZoneCommands::UpdateResources(update_resources) => update_resources.run(client).await, - } - } -} diff --git a/crates/ctl/src/cli/zone/resolve.rs b/crates/ctl/src/cli/zone/resolve.rs deleted file mode 100644 index 1e0381dd..00000000 --- a/crates/ctl/src/cli/zone/resolve.rs +++ /dev/null @@ -1,29 +0,0 @@ -use anyhow::Result; -use clap::Parser; -use krata::v1::control::{control_service_client::ControlServiceClient, ResolveZoneIdRequest}; - -use tonic::{transport::Channel, Request}; - -#[derive(Parser)] -#[command(about = "Resolve a zone name to a uuid")] -pub struct ZoneResolveCommand { - #[arg(help = "Zone name")] - zone: String, -} - -impl ZoneResolveCommand { - pub async fn run(self, mut client: ControlServiceClient) -> Result<()> { - let reply = client - .resolve_zone_id(Request::new(ResolveZoneIdRequest { - name: self.zone.clone(), - })) - .await? - .into_inner(); - if !reply.zone_id.is_empty() { - println!("{}", reply.zone_id); - } else { - std::process::exit(1); - } - Ok(()) - } -} diff --git a/crates/ctl/src/cli/zone/top.rs b/crates/ctl/src/cli/zone/top.rs deleted file mode 100644 index 5526cecb..00000000 --- a/crates/ctl/src/cli/zone/top.rs +++ /dev/null @@ -1,215 +0,0 @@ -use anyhow::Result; -use clap::Parser; -use krata::{events::EventStream, v1::control::control_service_client::ControlServiceClient}; -use std::{ - io::{self, stdout, Stdout}, - time::Duration, -}; -use tokio::select; -use tokio_stream::StreamExt; -use tonic::transport::Channel; - -use crossterm::{ - event::{Event, KeyCode, KeyEvent, KeyEventKind}, - execute, - terminal::*, -}; -use ratatui::{ - prelude::*, - symbols::border, - widgets::{ - block::{Position, Title}, - Block, Borders, Row, Table, TableState, - }, -}; - -use crate::{ - format::zone_state_text, - metrics::{ - lookup_metric_value, MultiMetricCollector, MultiMetricCollectorHandle, MultiMetricState, - }, -}; - -#[derive(Parser)] -#[command(about = "Dashboard for running zones")] -pub struct ZoneTopCommand {} - -pub type Tui = Terminal>; - -impl ZoneTopCommand { - pub async fn run( - self, - client: ControlServiceClient, - events: EventStream, - ) -> Result<()> { - let collector = MultiMetricCollector::new(client, events, Duration::from_millis(200))?; - let collector = collector.launch().await?; - let mut tui = ZoneTopCommand::init()?; - let mut app = ZoneTopApp { - metrics: MultiMetricState { zones: vec![] }, - exit: false, - table: TableState::new(), - }; - app.run(collector, &mut tui).await?; - ZoneTopCommand::restore()?; - Ok(()) - } - - pub fn init() -> io::Result { - execute!(stdout(), EnterAlternateScreen)?; - enable_raw_mode()?; - Terminal::new(CrosstermBackend::new(stdout())) - } - - pub fn restore() -> io::Result<()> { - execute!(stdout(), LeaveAlternateScreen)?; - disable_raw_mode()?; - Ok(()) - } -} - -pub struct ZoneTopApp { - table: TableState, - metrics: MultiMetricState, - exit: bool, -} - -impl ZoneTopApp { - pub async fn run( - &mut self, - mut collector: MultiMetricCollectorHandle, - terminal: &mut Tui, - ) -> Result<()> { - let mut events = crossterm::event::EventStream::new(); - - while !self.exit { - terminal.draw(|frame| self.render_frame(frame))?; - - select! { - x = collector.receiver.recv() => match x { - Some(state) => { - self.metrics = state; - }, - - None => { - break; - } - }, - - x = events.next() => match x { - Some(event) => { - let event = event?; - self.handle_event(event)?; - }, - - None => { - break; - } - } - } - } - Ok(()) - } - - fn render_frame(&mut self, frame: &mut Frame) { - frame.render_widget(self, frame.area()); - } - - fn handle_event(&mut self, event: Event) -> io::Result<()> { - match event { - Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { - self.handle_key_event(key_event) - } - _ => {} - }; - Ok(()) - } - - fn exit(&mut self) { - self.exit = true; - } - - fn handle_key_event(&mut self, key_event: KeyEvent) { - if let KeyCode::Char('q') = key_event.code { - self.exit() - } - } -} - -impl Widget for &mut ZoneTopApp { - fn render(self, area: Rect, buf: &mut Buffer) { - let title = Title::from(" krata isolation engine ".bold()); - let instructions = Title::from(vec![" Quit ".into(), " ".blue().bold()]); - let block = Block::default() - .title(title.alignment(Alignment::Center)) - .title( - instructions - .alignment(Alignment::Center) - .position(Position::Bottom), - ) - .borders(Borders::ALL) - .border_set(border::THICK); - - let mut rows = vec![]; - - for ms in &self.metrics.zones { - let Some(ref spec) = ms.zone.spec else { - continue; - }; - - let Some(ref status) = ms.zone.status else { - continue; - }; - - let memory_total = ms - .root - .as_ref() - .and_then(|root| lookup_metric_value(root, "system/memory/total")); - let memory_used = ms - .root - .as_ref() - .and_then(|root| lookup_metric_value(root, "system/memory/used")); - let memory_free = ms - .root - .as_ref() - .and_then(|root| lookup_metric_value(root, "system/memory/free")); - - let row = Row::new(vec![ - spec.name.clone(), - ms.zone.id.clone(), - zone_state_text(status.state()), - memory_total.unwrap_or_default(), - memory_used.unwrap_or_default(), - memory_free.unwrap_or_default(), - ]); - rows.push(row); - } - - let widths = [ - Constraint::Min(8), - Constraint::Min(8), - Constraint::Min(8), - Constraint::Min(8), - Constraint::Min(8), - Constraint::Min(8), - ]; - - let table = Table::new(rows, widths) - .header( - Row::new(vec![ - "name", - "id", - "status", - "total memory", - "used memory", - "free memory", - ]) - .style(Style::new().bold()) - .bottom_margin(1), - ) - .column_spacing(1) - .block(block); - - StatefulWidget::render(table, area, buf, &mut self.table); - } -} diff --git a/crates/ctl/src/cli/zone/update_resources.rs b/crates/ctl/src/cli/zone/update_resources.rs deleted file mode 100644 index d7d8f219..00000000 --- a/crates/ctl/src/cli/zone/update_resources.rs +++ /dev/null @@ -1,93 +0,0 @@ -use anyhow::Result; -use clap::Parser; -use krata::v1::{ - common::ZoneResourceSpec, - control::{control_service_client::ControlServiceClient, UpdateZoneResourcesRequest}, -}; - -use crate::cli::resolve_zone; -use krata::v1::control::GetZoneRequest; -use tonic::{transport::Channel, Request}; - -#[derive(Parser)] -#[command(about = "Update the available resources to a zone")] -pub struct ZoneUpdateResourcesCommand { - #[arg(help = "Zone to update resources of, either the name or the uuid")] - zone: String, - #[arg( - short = 'C', - long = "max-cpus", - default_value_t = 0, - help = "Maximum vCPUs available to the zone (0 means previous value)" - )] - max_cpus: u32, - #[arg( - short = 'c', - long = "target-cpus", - default_value_t = 0, - help = "Target vCPUs for the zone to use (0 means previous value)" - )] - target_cpus: u32, - #[arg( - short = 'M', - long = "max-memory", - default_value_t = 0, - help = "Maximum memory available to the zone, in megabytes (0 means previous value)" - )] - max_memory: u64, - #[arg( - short = 'm', - long = "target-memory", - default_value_t = 0, - help = "Target memory for the zone to use, in megabytes (0 means previous value)" - )] - target_memory: u64, -} - -impl ZoneUpdateResourcesCommand { - pub async fn run(self, mut client: ControlServiceClient) -> Result<()> { - let zone_id = resolve_zone(&mut client, &self.zone).await?; - let zone = client - .get_zone(GetZoneRequest { zone_id }) - .await? - .into_inner() - .zone - .unwrap_or_default(); - let active_resources = zone - .status - .clone() - .unwrap_or_default() - .resource_status - .unwrap_or_default() - .active_resources - .unwrap_or_default(); - client - .update_zone_resources(Request::new(UpdateZoneResourcesRequest { - zone_id: zone.id.clone(), - resources: Some(ZoneResourceSpec { - max_memory: if self.max_memory == 0 { - active_resources.max_memory - } else { - self.max_memory - }, - target_memory: if self.target_memory == 0 { - active_resources.target_memory - } else { - self.target_memory - }, - max_cpus: if self.max_cpus == 0 { - active_resources.max_cpus - } else { - self.max_cpus - }, - target_cpus: if self.target_cpus == 0 { - active_resources.target_cpus - } else { - self.target_cpus - }, - }), - })) - .await?; - Ok(()) - } -} diff --git a/crates/ctl/src/cli/zone/watch.rs b/crates/ctl/src/cli/zone/watch.rs deleted file mode 100644 index ed23cabe..00000000 --- a/crates/ctl/src/cli/zone/watch.rs +++ /dev/null @@ -1,63 +0,0 @@ -use anyhow::Result; -use clap::{Parser, ValueEnum}; -use krata::{ - events::EventStream, - v1::{common::Zone, control::watch_events_reply::Event}, -}; -use prost_reflect::ReflectMessage; -use serde_json::Value; - -use crate::format::{kv2line, proto2dynamic, proto2kv, zone_simple_line}; - -#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] -enum ZoneWatchFormat { - Simple, - Json, - KeyValue, -} - -#[derive(Parser)] -#[command(about = "Watch for zone changes")] -pub struct ZoneWatchCommand { - #[arg(short, long, default_value = "simple", help = "Output format")] - format: ZoneWatchFormat, -} - -impl ZoneWatchCommand { - pub async fn run(self, events: EventStream) -> Result<()> { - let mut stream = events.subscribe(); - loop { - let event = stream.recv().await?; - - let Event::ZoneChanged(changed) = event; - let zone = changed.zone.clone(); - self.print_event("zone.changed", changed, zone)?; - } - } - - fn print_event(&self, typ: &str, event: impl ReflectMessage, zone: Option) -> Result<()> { - match self.format { - ZoneWatchFormat::Simple => { - if let Some(zone) = zone { - println!("{}", zone_simple_line(&zone)); - } - } - - ZoneWatchFormat::Json => { - let message = proto2dynamic(event)?; - let mut value = serde_json::to_value(&message)?; - if let Value::Object(ref mut map) = value { - map.insert("event.type".to_string(), Value::String(typ.to_string())); - } - println!("{}", serde_json::to_string(&value)?); - } - - ZoneWatchFormat::KeyValue => { - let mut map = proto2kv(event)?; - map.insert("event.type".to_string(), typ.to_string()); - println!("{}", kv2line(map),); - } - } - Ok(()) - } -} diff --git a/crates/ctl/src/console.rs b/crates/ctl/src/console.rs deleted file mode 100644 index 1f8e9c5c..00000000 --- a/crates/ctl/src/console.rs +++ /dev/null @@ -1,263 +0,0 @@ -use anyhow::Result; -use async_stream::stream; -use crossterm::{ - terminal::{disable_raw_mode, enable_raw_mode, is_raw_mode_enabled}, - tty::IsTty, -}; -use krata::v1::common::ZoneState; -use krata::{ - events::EventStream, - v1::common::TerminalSize, - v1::control::{ - watch_events_reply::Event, ExecInsideZoneReply, ExecInsideZoneRequest, ZoneConsoleReply, - ZoneConsoleRequest, - }, -}; -use log::debug; -use tokio::{ - io::{stderr, stdin, stdout, AsyncReadExt, AsyncWriteExt}, - select, - task::JoinHandle, -}; -use tokio_stream::{Stream, StreamExt}; -use tonic::Streaming; - -pub struct StdioConsoleStream; - -enum ExecStdinSelect { - DataRead(std::io::Result), - TerminalResize, -} - -impl StdioConsoleStream { - pub async fn stdin_stream( - zone: String, - replay_history: bool, - ) -> impl Stream { - let mut stdin = stdin(); - stream! { - yield ZoneConsoleRequest { zone_id: zone, replay_history, data: vec![] }; - - let mut buffer = vec![0u8; 60]; - loop { - let size = match stdin.read(&mut buffer).await { - Ok(size) => size, - Err(error) => { - debug!("failed to read stdin: {}", error); - break; - } - }; - let data = buffer[0..size].to_vec(); - if size == 1 && buffer[0] == 0x1d { - break; - } - yield ZoneConsoleRequest { zone_id: String::default(), replay_history, data }; - } - } - } - - #[cfg(unix)] - pub async fn input_stream_exec( - initial: ExecInsideZoneRequest, - tty: bool, - ) -> impl Stream { - let mut stdin = stdin(); - stream! { - yield initial; - - let mut buffer = vec![0u8; 60]; - let mut terminal_size_change = if tty { - tokio::signal::unix::signal(tokio::signal::unix::SignalKind::window_change()).ok() - } else { - None - }; - let mut stdin_closed = false; - loop { - let selected = if let Some(ref mut terminal_size_change) = terminal_size_change { - if stdin_closed { - select! { - _ = terminal_size_change.recv() => ExecStdinSelect::TerminalResize, - } - } else { - select! { - result = stdin.read(&mut buffer) => ExecStdinSelect::DataRead(result), - _ = terminal_size_change.recv() => ExecStdinSelect::TerminalResize, - } - } - } else { - select! { - result = stdin.read(&mut buffer) => ExecStdinSelect::DataRead(result), - } - }; - - match selected { - ExecStdinSelect::DataRead(result) => { - match result { - Ok(size) => { - let stdin = buffer[0..size].to_vec(); - if size == 1 && buffer[0] == 0x1d { - break; - } - stdin_closed = size == 0; - yield ExecInsideZoneRequest { zone_id: String::default(), task: None, terminal_size: None, stdin, stdin_closed, }; - }, - Err(error) => { - debug!("failed to read stdin: {}", error); - break; - } - } - }, - ExecStdinSelect::TerminalResize => { - if let Ok((columns, rows)) = crossterm::terminal::size() { - yield ExecInsideZoneRequest { zone_id: String::default(), task: None, terminal_size: Some(TerminalSize { - rows: rows as u32, - columns: columns as u32, - }), stdin: vec![], stdin_closed: false, }; - } - } - } - } - } - } - - #[cfg(not(unix))] - pub async fn input_stream_exec( - initial: ExecInsideZoneRequest, - _tty: bool, - ) -> impl Stream { - let mut stdin = stdin(); - stream! { - yield initial; - - let mut buffer = vec![0u8; 60]; - let mut stdin_closed = false; - loop { - let selected = select! { - result = stdin.read(&mut buffer) => ExecStdinSelect::DataRead(result), - }; - - match selected { - ExecStdinSelect::DataRead(result) => { - match result { - Ok(size) => { - let stdin = buffer[0..size].to_vec(); - if size == 1 && buffer[0] == 0x1d { - break; - } - stdin_closed = size == 0; - yield ExecInsideZoneRequest { zone_id: String::default(), task: None, terminal_size: None, stdin, stdin_closed, }; - }, - Err(error) => { - debug!("failed to read stdin: {}", error); - break; - } - } - }, - _ => { - continue; - } - } - } - } - } - - pub async fn stdout(mut stream: Streaming, raw: bool) -> Result<()> { - if raw && stdin().is_tty() { - enable_raw_mode()?; - StdioConsoleStream::register_terminal_restore_hook()?; - } - let mut stdout = stdout(); - while let Some(reply) = stream.next().await { - let reply = reply?; - if reply.data.is_empty() { - continue; - } - stdout.write_all(&reply.data).await?; - stdout.flush().await?; - } - Ok(()) - } - - pub async fn exec_output(mut stream: Streaming, raw: bool) -> Result { - if raw { - enable_raw_mode()?; - StdioConsoleStream::register_terminal_restore_hook()?; - } - let mut stdout = stdout(); - let mut stderr = stderr(); - while let Some(reply) = stream.next().await { - let reply = reply?; - if !reply.stdout.is_empty() { - stdout.write_all(&reply.stdout).await?; - stdout.flush().await?; - } - - if !reply.stderr.is_empty() { - stderr.write_all(&reply.stderr).await?; - stderr.flush().await?; - } - - if reply.exited { - return if reply.error.is_empty() { - Ok(reply.exit_code) - } else { - StdioConsoleStream::restore_terminal_mode(); - stderr - .write_all(format!("Error: exec failed: {}\n", reply.error).as_bytes()) - .await?; - stderr.flush().await?; - Ok(-1) - }; - } - } - Ok(-1) - } - - pub async fn zone_exit_hook( - id: String, - events: EventStream, - ) -> Result>> { - Ok(tokio::task::spawn(async move { - let mut stream = events.subscribe(); - while let Ok(event) = stream.recv().await { - let Event::ZoneChanged(changed) = event; - let Some(zone) = changed.zone else { - continue; - }; - - let Some(status) = zone.status else { - continue; - }; - - if zone.id != id { - continue; - } - - if let Some(exit_status) = status.exit_status { - return Some(exit_status.code); - } - - let state = status.state(); - if state == ZoneState::Destroying || state == ZoneState::Destroyed { - return Some(10); - } - } - None - })) - } - - fn register_terminal_restore_hook() -> Result<()> { - if stdin().is_tty() { - ctrlc::set_handler(move || { - StdioConsoleStream::restore_terminal_mode(); - })?; - } - Ok(()) - } - - pub fn restore_terminal_mode() { - if is_raw_mode_enabled().unwrap_or(false) { - let _ = disable_raw_mode(); - } - } -} diff --git a/crates/ctl/src/format.rs b/crates/ctl/src/format.rs deleted file mode 100644 index 842a6342..00000000 --- a/crates/ctl/src/format.rs +++ /dev/null @@ -1,164 +0,0 @@ -use std::{collections::HashMap, time::Duration}; - -use anyhow::Result; -use fancy_duration::FancyDuration; -use human_bytes::human_bytes; -use prost_reflect::{DynamicMessage, ReflectMessage}; -use prost_types::Value; -use termtree::Tree; - -use krata::v1::common::{Zone, ZoneMetricFormat, ZoneMetricNode, ZoneState}; - -pub fn proto2dynamic(proto: impl ReflectMessage) -> Result { - Ok(DynamicMessage::decode( - proto.descriptor(), - proto.encode_to_vec().as_slice(), - )?) -} - -pub fn value2kv(value: serde_json::Value) -> Result> { - let mut map = HashMap::new(); - fn crawl(prefix: String, map: &mut HashMap, value: serde_json::Value) { - fn dot(prefix: &str, next: String) -> String { - if prefix.is_empty() { - next.to_string() - } else { - format!("{}.{}", prefix, next) - } - } - - match value { - serde_json::Value::Null => { - map.insert(prefix, "null".to_string()); - } - - serde_json::Value::String(value) => { - map.insert(prefix, value); - } - - serde_json::Value::Bool(value) => { - map.insert(prefix, value.to_string()); - } - - serde_json::Value::Number(value) => { - map.insert(prefix, value.to_string()); - } - - serde_json::Value::Array(value) => { - for (i, item) in value.into_iter().enumerate() { - let next = dot(&prefix, i.to_string()); - crawl(next, map, item); - } - } - - serde_json::Value::Object(value) => { - for (key, item) in value { - let next = dot(&prefix, key); - crawl(next, map, item); - } - } - } - } - crawl("".to_string(), &mut map, value); - Ok(map) -} - -pub fn proto2kv(proto: impl ReflectMessage) -> Result> { - let message = proto2dynamic(proto)?; - let value = serde_json::to_value(message)?; - value2kv(value) -} - -pub fn kv2line(map: HashMap) -> String { - map.iter() - .map(|(k, v)| format!("{}=\"{}\"", k, v.replace('"', "\\\""))) - .collect::>() - .join(" ") -} - -pub fn zone_state_text(status: ZoneState) -> String { - match status { - ZoneState::Creating => "creating", - ZoneState::Created => "created", - ZoneState::Destroying => "destroying", - ZoneState::Destroyed => "destroyed", - ZoneState::Exited => "exited", - ZoneState::Failed => "failed", - _ => "unknown", - } - .to_string() -} - -pub fn zone_simple_line(zone: &Zone) -> String { - let state = zone_state_text( - zone.status - .as_ref() - .map(|x| x.state()) - .unwrap_or(ZoneState::Unknown), - ); - let name = zone.spec.as_ref().map(|x| x.name.as_str()).unwrap_or(""); - let network_status = zone.status.as_ref().and_then(|x| x.network_status.as_ref()); - let ipv4 = network_status.map(|x| x.zone_ipv4.as_str()).unwrap_or(""); - let ipv6 = network_status.map(|x| x.zone_ipv6.as_str()).unwrap_or(""); - format!("{}\t{}\t{}\t{}\t{}", zone.id, state, name, ipv4, ipv6) -} - -fn metrics_value_string(value: Value) -> String { - proto2dynamic(value) - .map(|x| serde_json::to_string(&x).ok()) - .ok() - .flatten() - .unwrap_or_default() -} - -fn metrics_value_numeric(value: Value) -> f64 { - let string = metrics_value_string(value); - string.parse::().ok().unwrap_or(f64::NAN) -} - -pub fn metrics_value_pretty(value: Value, format: ZoneMetricFormat) -> String { - match format { - ZoneMetricFormat::Bytes => human_bytes(metrics_value_numeric(value)), - ZoneMetricFormat::Integer => (metrics_value_numeric(value) as u64).to_string(), - ZoneMetricFormat::DurationSeconds => { - FancyDuration(Duration::from_secs_f64(metrics_value_numeric(value))).to_string() - } - _ => metrics_value_string(value), - } -} - -fn metrics_flat_internal(prefix: &str, node: ZoneMetricNode, map: &mut HashMap) { - if let Some(value) = node.value { - map.insert(prefix.to_string(), metrics_value_string(value)); - } - - for child in node.children { - let path = if prefix.is_empty() { - child.name.to_string() - } else { - format!("{}.{}", prefix, child.name) - }; - metrics_flat_internal(&path, child, map); - } -} - -pub fn metrics_flat(root: ZoneMetricNode) -> HashMap { - let mut map = HashMap::new(); - metrics_flat_internal("", root, &mut map); - map -} - -pub fn metrics_tree(node: ZoneMetricNode) -> Tree { - let mut name = node.name.to_string(); - let format = node.format(); - if let Some(value) = node.value { - let value_string = metrics_value_pretty(value, format); - name.push_str(&format!(": {}", value_string)); - } - - let mut tree = Tree::new(name); - for child in node.children { - tree.push(metrics_tree(child)); - } - tree -} diff --git a/crates/ctl/src/lib.rs b/crates/ctl/src/lib.rs deleted file mode 100644 index 82ee22d7..00000000 --- a/crates/ctl/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod cli; -pub mod console; -pub mod format; -pub mod metrics; -pub mod pull; diff --git a/crates/ctl/src/metrics.rs b/crates/ctl/src/metrics.rs deleted file mode 100644 index 0a7a76ec..00000000 --- a/crates/ctl/src/metrics.rs +++ /dev/null @@ -1,158 +0,0 @@ -use crate::format::metrics_value_pretty; -use anyhow::Result; -use krata::v1::common::ZoneState; -use krata::{ - events::EventStream, - v1::{ - common::{Zone, ZoneMetricNode}, - control::{ - control_service_client::ControlServiceClient, watch_events_reply::Event, - ListZonesRequest, ReadZoneMetricsRequest, - }, - }, -}; -use log::error; -use std::time::Duration; -use tokio::{ - select, - sync::mpsc::{channel, Receiver, Sender}, - task::JoinHandle, - time::{sleep, timeout}, -}; -use tonic::transport::Channel; - -pub struct MetricState { - pub zone: Zone, - pub root: Option, -} - -pub struct MultiMetricState { - pub zones: Vec, -} - -pub struct MultiMetricCollector { - client: ControlServiceClient, - events: EventStream, - period: Duration, -} - -pub struct MultiMetricCollectorHandle { - pub receiver: Receiver, - task: JoinHandle<()>, -} - -impl Drop for MultiMetricCollectorHandle { - fn drop(&mut self) { - self.task.abort(); - } -} - -impl MultiMetricCollector { - pub fn new( - client: ControlServiceClient, - events: EventStream, - period: Duration, - ) -> Result { - Ok(MultiMetricCollector { - client, - events, - period, - }) - } - - pub async fn launch(mut self) -> Result { - let (sender, receiver) = channel::(100); - let task = tokio::task::spawn(async move { - if let Err(error) = self.process(sender).await { - error!("failed to process multi metric collector: {}", error); - } - }); - Ok(MultiMetricCollectorHandle { receiver, task }) - } - - pub async fn process(&mut self, sender: Sender) -> Result<()> { - let mut events = self.events.subscribe(); - let mut zones: Vec = self - .client - .list_zones(ListZonesRequest {}) - .await? - .into_inner() - .zones; - loop { - let collect = select! { - x = events.recv() => match x { - Ok(event) => { - let Event::ZoneChanged(changed) = event; - let Some(zone) = changed.zone else { - continue; - }; - let Some(ref status) = zone.status else { - continue; - }; - zones.retain(|x| x.id != zone.id); - if status.state() != ZoneState::Destroying { - zones.push(zone); - } - false - }, - - Err(error) => { - return Err(error.into()); - } - }, - - _ = sleep(self.period) => { - true - } - }; - - if !collect { - continue; - } - - let mut metrics = Vec::new(); - for zone in &zones { - let Some(ref status) = zone.status else { - continue; - }; - - if status.state() != ZoneState::Created { - continue; - } - - let root = timeout( - Duration::from_secs(5), - self.client.read_zone_metrics(ReadZoneMetricsRequest { - zone_id: zone.id.clone(), - }), - ) - .await - .ok() - .and_then(|x| x.ok()) - .map(|x| x.into_inner()) - .and_then(|x| x.root); - metrics.push(MetricState { - zone: zone.clone(), - root, - }); - } - sender.send(MultiMetricState { zones: metrics }).await?; - } - } -} - -pub fn lookup<'a>(node: &'a ZoneMetricNode, path: &str) -> Option<&'a ZoneMetricNode> { - let Some((what, b)) = path.split_once('/') else { - return node.children.iter().find(|x| x.name == path); - }; - let next = node.children.iter().find(|x| x.name == what)?; - return lookup(next, b); -} - -pub fn lookup_metric_value(node: &ZoneMetricNode, path: &str) -> Option { - lookup(node, path).and_then(|x| { - x.value - .as_ref() - .map(|v| metrics_value_pretty(v.clone(), x.format())) - }) -} diff --git a/crates/ctl/src/pull.rs b/crates/ctl/src/pull.rs deleted file mode 100644 index 098d40ea..00000000 --- a/crates/ctl/src/pull.rs +++ /dev/null @@ -1,268 +0,0 @@ -use std::{ - collections::{hash_map::Entry, HashMap}, - time::Duration, -}; - -use anyhow::{anyhow, Result}; -use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; -use krata::v1::control::{ - image_progress_indication::Indication, ImageProgressIndication, ImageProgressLayerPhase, - ImageProgressPhase, PullImageReply, -}; -use tokio_stream::StreamExt; -use tonic::Streaming; - -const SPINNER_STRINGS: &[&str] = &[ - "[= ]", - "[ = ]", - "[ = ]", - "[ = ]", - "[ = ]", - "[ = ]", - "[ = ]", - "[ = ]", - "[ = ]", - "[ = ]", - "[ = ]", - "[ = ]", - "[ = ]", - "[ = ]", - "[ = ]", - "[ = ]", - "[ = ]", - "[ = ]", - "[ = ]", - "[ =]", - "[====================]", -]; - -fn progress_bar_for_indication(indication: &ImageProgressIndication) -> Option { - match indication.indication.as_ref() { - Some(Indication::Hidden(_)) | None => None, - Some(Indication::Bar(indic)) => { - let bar = ProgressBar::new(indic.total); - bar.enable_steady_tick(Duration::from_millis(100)); - Some(bar) - } - Some(Indication::Spinner(_)) => { - let bar = ProgressBar::new_spinner(); - bar.enable_steady_tick(Duration::from_millis(100)); - Some(bar) - } - Some(Indication::Completed(indic)) => { - let bar = ProgressBar::new_spinner(); - bar.enable_steady_tick(Duration::from_millis(100)); - if !indic.message.is_empty() { - bar.finish_with_message(indic.message.clone()); - } else { - bar.finish() - } - Some(bar) - } - } -} - -fn configure_for_indication( - bar: &mut ProgressBar, - multi_progress: &mut MultiProgress, - indication: &ImageProgressIndication, - top_phase: Option, - layer_phase: Option, - layer_id: Option<&str>, -) { - let prefix = if let Some(phase) = top_phase { - match phase { - ImageProgressPhase::Unknown => "unknown", - ImageProgressPhase::Started => "started", - ImageProgressPhase::Resolving => "resolving", - ImageProgressPhase::Resolved => "resolved", - ImageProgressPhase::ConfigDownload => "downloading", - ImageProgressPhase::LayerDownload => "downloading", - ImageProgressPhase::Assemble => "assembling", - ImageProgressPhase::Pack => "packing", - ImageProgressPhase::Complete => "complete", - } - } else if let Some(phase) = layer_phase { - match phase { - ImageProgressLayerPhase::Unknown => "unknown", - ImageProgressLayerPhase::Waiting => "waiting", - ImageProgressLayerPhase::Downloading => "downloading", - ImageProgressLayerPhase::Downloaded => "downloaded", - ImageProgressLayerPhase::Extracting => "extracting", - ImageProgressLayerPhase::Extracted => "extracted", - } - } else { - "" - }; - let prefix = prefix.to_string(); - - let id = if let Some(layer_id) = layer_id { - let hash = if let Some((_, hash)) = layer_id.split_once(':') { - hash - } else { - "unknown" - }; - let small_hash = if hash.len() > 10 { &hash[0..10] } else { hash }; - Some(format!("{:width$}", small_hash, width = 10)) - } else { - None - }; - - let prefix = if let Some(id) = id { - format!("{} {:width$}", id, prefix, width = 11) - } else { - format!(" {:width$}", prefix, width = 11) - }; - - match indication.indication.as_ref() { - Some(Indication::Hidden(_)) | None => { - multi_progress.remove(bar); - return; - } - Some(Indication::Bar(indic)) => { - if indic.is_bytes { - bar.set_style(ProgressStyle::with_template("{prefix} [{bar:20}] {msg} {binary_bytes}/{binary_total_bytes} ({binary_bytes_per_sec}) eta: {eta}").unwrap().progress_chars("=>-")); - } else { - bar.set_style( - ProgressStyle::with_template( - "{prefix} [{bar:20} {msg} {human_pos}/{human_len} ({per_sec}/sec)", - ) - .unwrap() - .progress_chars("=>-"), - ); - } - bar.set_message(indic.message.clone()); - bar.set_position(indic.current); - bar.set_length(indic.total); - } - Some(Indication::Spinner(indic)) => { - bar.set_style( - ProgressStyle::with_template("{prefix} {spinner} {msg}") - .unwrap() - .tick_strings(SPINNER_STRINGS), - ); - bar.set_message(indic.message.clone()); - } - Some(Indication::Completed(indic)) => { - if bar.is_finished() { - return; - } - bar.disable_steady_tick(); - bar.set_message(indic.message.clone()); - if indic.total != 0 { - bar.set_position(indic.total); - bar.set_length(indic.total); - } - if bar.style().get_tick_str(0).contains('=') { - bar.set_style( - ProgressStyle::with_template("{prefix} {spinner} {msg}") - .unwrap() - .tick_strings(SPINNER_STRINGS), - ); - bar.finish_with_message(indic.message.clone()); - } else if indic.is_bytes { - bar.set_style( - ProgressStyle::with_template("{prefix} [{bar:20}] {msg} {binary_total_bytes}") - .unwrap() - .progress_chars("=>-"), - ); - } else { - bar.set_style( - ProgressStyle::with_template("{prefix} [{bar:20}] {msg}") - .unwrap() - .progress_chars("=>-"), - ); - } - bar.tick(); - bar.enable_steady_tick(Duration::from_millis(100)); - } - }; - - bar.set_prefix(prefix); - bar.tick(); -} - -pub async fn pull_interactive_progress( - mut stream: Streaming, -) -> Result { - let mut multi_progress = MultiProgress::new(); - multi_progress.set_move_cursor(false); - let mut progresses = HashMap::new(); - - while let Some(reply) = stream.next().await { - let reply = match reply { - Ok(reply) => reply, - Err(error) => { - multi_progress.clear()?; - return Err(error.into()); - } - }; - - if reply.progress.is_none() && !reply.digest.is_empty() { - multi_progress.clear()?; - return Ok(reply); - } - - let Some(oci) = reply.progress else { - continue; - }; - - for layer in &oci.layers { - let Some(ref indication) = layer.indication else { - continue; - }; - - let bar = match progresses.entry(layer.id.clone()) { - Entry::Occupied(entry) => Some(entry.into_mut()), - - Entry::Vacant(entry) => { - if let Some(bar) = progress_bar_for_indication(indication) { - multi_progress.add(bar.clone()); - Some(entry.insert(bar)) - } else { - None - } - } - }; - - if let Some(bar) = bar { - configure_for_indication( - bar, - &mut multi_progress, - indication, - None, - Some(layer.phase()), - Some(&layer.id), - ); - } - } - - if let Some(ref indication) = oci.indication { - let bar = match progresses.entry("root".to_string()) { - Entry::Occupied(entry) => Some(entry.into_mut()), - - Entry::Vacant(entry) => { - if let Some(bar) = progress_bar_for_indication(indication) { - multi_progress.add(bar.clone()); - Some(entry.insert(bar)) - } else { - None - } - } - }; - - if let Some(bar) = bar { - configure_for_indication( - bar, - &mut multi_progress, - indication, - Some(oci.phase()), - None, - None, - ); - } - } - } - multi_progress.clear()?; - Err(anyhow!("never received final reply for image pull")) -} diff --git a/crates/daemon/Cargo.toml b/crates/daemon/Cargo.toml deleted file mode 100644 index fbfc1886..00000000 --- a/crates/daemon/Cargo.toml +++ /dev/null @@ -1,44 +0,0 @@ -[package] -name = "krata-daemon" -description = "Daemon for the krata isolation engine" -license.workspace = true -version.workspace = true -homepage.workspace = true -repository.workspace = true -edition = "2021" -resolver = "2" - -[dependencies] -krata-advmac = { workspace = true } -anyhow = { workspace = true } -async-stream = { workspace = true } -async-trait = { workspace = true } -bytes = { workspace = true } -circular-buffer = { workspace = true } -clap = { workspace = true } -env_logger = { workspace = true } -futures = { workspace = true } -ipnetwork = { workspace = true } -krata = { path = "../krata", version = "^0.0.21" } -krata-oci = { path = "../oci", version = "^0.0.21" } -krata-runtime = { path = "../runtime", version = "^0.0.21" } -log = { workspace = true } -prost = { workspace = true } -redb = { workspace = true } -scopeguard = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -signal-hook = { workspace = true } -tokio = { workspace = true } -tokio-stream = { workspace = true } -toml = { workspace = true } -krata-tokio-tar = { workspace = true } -tonic = { workspace = true, features = ["tls"] } -uuid = { workspace = true } - -[lib] -name = "kratad" - -[[bin]] -name = "kratad" -path = "bin/daemon.rs" diff --git a/crates/daemon/bin/daemon.rs b/crates/daemon/bin/daemon.rs deleted file mode 100644 index ee9dd856..00000000 --- a/crates/daemon/bin/daemon.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::{ - net::{SocketAddr, TcpStream}, - str::FromStr, - sync::{atomic::AtomicBool, Arc}, -}; - -use anyhow::Result; -use clap::Parser; -use env_logger::fmt::Target; -use log::LevelFilter; - -use kratad::command::DaemonCommand; - -#[tokio::main(flavor = "multi_thread", worker_threads = 10)] -async fn main() -> Result<()> { - let mut builder = env_logger::Builder::new(); - builder - .filter_level(LevelFilter::Info) - .parse_default_env() - .filter(Some("backhand::filesystem::writer"), LevelFilter::Warn); - - if let Ok(f_addr) = std::env::var("KRATA_FLUENT_ADDR") { - let target = SocketAddr::from_str(f_addr.as_str())?; - builder.target(Target::Pipe(Box::new(TcpStream::connect(target)?))); - } - - builder.init(); - - mask_sighup()?; - - let command = DaemonCommand::parse(); - command.run().await -} - -fn mask_sighup() -> Result<()> { - let flag = Arc::new(AtomicBool::new(false)); - signal_hook::flag::register(signal_hook::consts::SIGHUP, flag)?; - Ok(()) -} diff --git a/crates/daemon/src/command.rs b/crates/daemon/src/command.rs deleted file mode 100644 index 82f179ad..00000000 --- a/crates/daemon/src/command.rs +++ /dev/null @@ -1,36 +0,0 @@ -use anyhow::Result; -use clap::{CommandFactory, Parser}; -use krata::dial::ControlDialAddress; -use std::str::FromStr; - -use crate::Daemon; - -#[derive(Parser)] -#[command(version, about = "krata isolation engine daemon")] -pub struct DaemonCommand { - #[arg( - short, - long, - default_value = "unix:///var/lib/krata/daemon.socket", - help = "Listen address" - )] - listen: String, - #[arg(short, long, default_value = "/var/lib/krata", help = "Storage path")] - store: String, -} - -impl DaemonCommand { - pub async fn run(self) -> Result<()> { - let addr = ControlDialAddress::from_str(&self.listen)?; - let mut daemon = Daemon::new(self.store.clone()).await?; - daemon.listen(addr).await?; - Ok(()) - } - - pub fn version() -> String { - DaemonCommand::command() - .get_version() - .unwrap_or("unknown") - .to_string() - } -} diff --git a/crates/daemon/src/config.rs b/crates/daemon/src/config.rs deleted file mode 100644 index 74ed530f..00000000 --- a/crates/daemon/src/config.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::{collections::HashMap, path::Path}; - -use anyhow::Result; -use serde::{Deserialize, Serialize}; -use tokio::fs; - -#[derive(Serialize, Deserialize, Clone, Debug, Default)] -pub struct DaemonConfig { - #[serde(default)] - pub oci: OciConfig, - #[serde(default)] - pub pci: DaemonPciConfig, - #[serde(default = "default_network")] - pub network: DaemonNetworkConfig, -} - -#[derive(Serialize, Deserialize, Clone, Debug, Default)] -pub struct OciConfig { - #[serde(default)] - pub seed: Option, -} - -#[derive(Serialize, Deserialize, Clone, Debug, Default)] -pub struct DaemonPciConfig { - #[serde(default)] - pub devices: HashMap, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct DaemonPciDeviceConfig { - pub locations: Vec, - #[serde(default)] - pub permissive: bool, - #[serde(default)] - #[serde(rename = "msi-translate")] - pub msi_translate: bool, - #[serde(default)] - #[serde(rename = "power-management")] - pub power_management: bool, - #[serde(default)] - #[serde(rename = "rdm-reserve-policy")] - pub rdm_reserve_policy: DaemonPciDeviceRdmReservePolicy, -} - -#[derive(Serialize, Deserialize, Clone, Debug, Default)] -pub enum DaemonPciDeviceRdmReservePolicy { - #[default] - #[serde(rename = "strict")] - Strict, - #[serde(rename = "relaxed")] - Relaxed, -} - -#[derive(Serialize, Deserialize, Clone, Debug, Default)] -pub struct DaemonNetworkConfig { - #[serde(default = "default_network_nameservers")] - pub nameservers: Vec, - #[serde(default = "default_network_ipv4")] - pub ipv4: DaemonIpv4NetworkConfig, - #[serde(default = "default_network_ipv6")] - pub ipv6: DaemonIpv6NetworkConfig, -} - -#[derive(Serialize, Deserialize, Clone, Debug, Default)] -pub struct DaemonIpv4NetworkConfig { - #[serde(default = "default_network_ipv4_subnet")] - pub subnet: String, -} - -#[derive(Serialize, Deserialize, Clone, Debug, Default)] -pub struct DaemonIpv6NetworkConfig { - #[serde(default = "default_network_ipv6_subnet")] - pub subnet: String, -} - -fn default_network() -> DaemonNetworkConfig { - DaemonNetworkConfig { - nameservers: default_network_nameservers(), - ipv4: default_network_ipv4(), - ipv6: default_network_ipv6(), - } -} - -fn default_network_nameservers() -> Vec { - vec![ - "1.1.1.1".to_string(), - "1.0.0.1".to_string(), - "2606:4700:4700::1111".to_string(), - "2606:4700:4700::1001".to_string(), - ] -} - -fn default_network_ipv4() -> DaemonIpv4NetworkConfig { - DaemonIpv4NetworkConfig { - subnet: default_network_ipv4_subnet(), - } -} - -fn default_network_ipv4_subnet() -> String { - "10.75.0.0/16".to_string() -} - -fn default_network_ipv6() -> DaemonIpv6NetworkConfig { - DaemonIpv6NetworkConfig { - subnet: default_network_ipv6_subnet(), - } -} - -fn default_network_ipv6_subnet() -> String { - "fdd4:1476:6c7e::/48".to_string() -} - -impl DaemonConfig { - pub async fn load(path: &Path) -> Result { - if !path.exists() { - let config: DaemonConfig = toml::from_str("")?; - let content = toml::to_string_pretty(&config)?; - fs::write(&path, content).await?; - } - let content = fs::read_to_string(path).await?; - let config: DaemonConfig = toml::from_str(&content)?; - Ok(config) - } -} diff --git a/crates/daemon/src/console.rs b/crates/daemon/src/console.rs deleted file mode 100644 index f06e1c6f..00000000 --- a/crates/daemon/src/console.rs +++ /dev/null @@ -1,164 +0,0 @@ -use std::{collections::HashMap, sync::Arc}; - -use anyhow::{anyhow, Result}; -use circular_buffer::CircularBuffer; -use kratart::channel::ChannelService; -use log::error; -use tokio::{ - sync::{ - mpsc::{error::TrySendError, Receiver, Sender}, - Mutex, - }, - task::JoinHandle, -}; -use uuid::Uuid; - -use crate::zlt::ZoneLookupTable; - -const CONSOLE_BUFFER_SIZE: usize = 1024 * 1024; -type RawConsoleBuffer = CircularBuffer; -type ConsoleBuffer = Box; - -type ListenerMap = Arc>>>>>; -type BufferMap = Arc>>; - -#[derive(Clone)] -pub struct DaemonConsoleHandle { - zlt: ZoneLookupTable, - listeners: ListenerMap, - buffers: BufferMap, - sender: Sender<(u32, Vec)>, - task: Arc>, -} - -#[derive(Clone)] -pub struct DaemonConsoleAttachHandle { - pub initial: Vec, - listeners: ListenerMap, - sender: Sender<(u32, Vec)>, - domid: u32, -} - -impl DaemonConsoleAttachHandle { - pub async fn unsubscribe(&self) -> Result<()> { - let mut guard = self.listeners.lock().await; - let _ = guard.remove(&self.domid); - Ok(()) - } - - pub async fn send(&self, data: Vec) -> Result<()> { - Ok(self.sender.send((self.domid, data)).await?) - } -} - -impl DaemonConsoleHandle { - pub async fn attach( - &self, - uuid: Uuid, - sender: Sender>, - ) -> Result { - let Some(domid) = self.zlt.lookup_domid_by_uuid(&uuid).await else { - return Err(anyhow!("unable to find domain {}", uuid)); - }; - let buffers = self.buffers.lock().await; - let buffer = buffers.get(&domid).map(|x| x.to_vec()).unwrap_or_default(); - drop(buffers); - let mut listeners = self.listeners.lock().await; - let senders = listeners.entry(domid).or_default(); - senders.push(sender); - Ok(DaemonConsoleAttachHandle { - initial: buffer, - sender: self.sender.clone(), - listeners: self.listeners.clone(), - domid, - }) - } -} - -impl Drop for DaemonConsoleHandle { - fn drop(&mut self) { - if Arc::strong_count(&self.task) <= 1 { - self.task.abort(); - } - } -} - -pub struct DaemonConsole { - zlt: ZoneLookupTable, - listeners: ListenerMap, - buffers: BufferMap, - receiver: Receiver<(u32, Option>)>, - sender: Sender<(u32, Vec)>, - task: JoinHandle<()>, -} - -impl DaemonConsole { - pub async fn new(zlt: ZoneLookupTable) -> Result { - let (service, sender, receiver) = - ChannelService::new("krata-console".to_string(), Some(0)).await?; - let task = service.launch().await?; - let listeners = Arc::new(Mutex::new(HashMap::new())); - let buffers = Arc::new(Mutex::new(HashMap::new())); - Ok(DaemonConsole { - zlt, - listeners, - buffers, - receiver, - sender, - task, - }) - } - - pub async fn launch(mut self) -> Result { - let zlt = self.zlt.clone(); - let listeners = self.listeners.clone(); - let buffers = self.buffers.clone(); - let sender = self.sender.clone(); - let task = tokio::task::spawn(async move { - if let Err(error) = self.process().await { - error!("failed to process console: {}", error); - } - }); - Ok(DaemonConsoleHandle { - zlt, - listeners, - buffers, - sender, - task: Arc::new(task), - }) - } - - async fn process(&mut self) -> Result<()> { - loop { - let Some((domid, data)) = self.receiver.recv().await else { - break; - }; - - let mut buffers = self.buffers.lock().await; - if let Some(data) = data { - let buffer = buffers - .entry(domid) - .or_insert_with_key(|_| RawConsoleBuffer::boxed()); - buffer.extend_from_slice(&data); - drop(buffers); - let mut listeners = self.listeners.lock().await; - if let Some(senders) = listeners.get_mut(&domid) { - senders.retain(|sender| { - !matches!(sender.try_send(data.to_vec()), Err(TrySendError::Closed(_))) - }); - } - } else { - buffers.remove(&domid); - let mut listeners = self.listeners.lock().await; - listeners.remove(&domid); - } - } - Ok(()) - } -} - -impl Drop for DaemonConsole { - fn drop(&mut self) { - self.task.abort(); - } -} diff --git a/crates/daemon/src/control/attach_zone_console.rs b/crates/daemon/src/control/attach_zone_console.rs deleted file mode 100644 index 1b305d7b..00000000 --- a/crates/daemon/src/control/attach_zone_console.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::pin::Pin; -use std::str::FromStr; - -use anyhow::{anyhow, Result}; -use async_stream::try_stream; -use tokio::select; -use tokio::sync::mpsc::channel; -use tokio_stream::{Stream, StreamExt}; -use tonic::{Status, Streaming}; -use uuid::Uuid; - -use krata::v1::control::{ZoneConsoleReply, ZoneConsoleRequest}; - -use crate::console::DaemonConsoleHandle; -use crate::control::ApiError; - -enum ConsoleDataSelect { - Read(Option>), - Write(Option>), -} - -pub struct AttachZoneConsoleRpc { - console: DaemonConsoleHandle, -} - -impl AttachZoneConsoleRpc { - pub fn new(console: DaemonConsoleHandle) -> Self { - Self { console } - } - - pub async fn process( - self, - mut input: Streaming, - ) -> Result> + Send + 'static>>> - { - let Some(request) = input.next().await else { - return Err(anyhow!("expected to have at least one request")); - }; - let request = request?; - let uuid = Uuid::from_str(&request.zone_id)?; - let (sender, mut receiver) = channel(100); - let console = self - .console - .attach(uuid, sender) - .await - .map_err(|error| anyhow!("failed to attach to console: {}", error))?; - - let output = try_stream! { - if request.replay_history { - yield ZoneConsoleReply { data: console.initial.clone(), }; - } - loop { - let what = select! { - x = receiver.recv() => ConsoleDataSelect::Read(x), - x = input.next() => ConsoleDataSelect::Write(x), - }; - - match what { - ConsoleDataSelect::Read(Some(data)) => { - yield ZoneConsoleReply { data, }; - }, - - ConsoleDataSelect::Read(None) => { - break; - } - - ConsoleDataSelect::Write(Some(request)) => { - let request = request?; - if !request.data.is_empty() { - console.send(request.data).await.map_err(|error| ApiError { - message: error.to_string(), - })?; - } - }, - - ConsoleDataSelect::Write(None) => { - break; - } - } - } - }; - Ok(Box::pin(output)) - } -} diff --git a/crates/daemon/src/control/create_zone.rs b/crates/daemon/src/control/create_zone.rs deleted file mode 100644 index 00375eff..00000000 --- a/crates/daemon/src/control/create_zone.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::db::zone::ZoneStore; -use crate::zlt::ZoneLookupTable; -use anyhow::{anyhow, Result}; -use krata::v1::common::{Zone, ZoneState, ZoneStatus}; -use krata::v1::control::{CreateZoneReply, CreateZoneRequest}; -use tokio::sync::mpsc::Sender; -use uuid::Uuid; - -pub struct CreateZoneRpc { - zones: ZoneStore, - zlt: ZoneLookupTable, - zone_reconciler_notify: Sender, -} - -impl CreateZoneRpc { - pub fn new( - zones: ZoneStore, - zlt: ZoneLookupTable, - zone_reconciler_notify: Sender, - ) -> Self { - Self { - zones, - zlt, - zone_reconciler_notify, - } - } - - pub async fn process(self, request: CreateZoneRequest) -> Result { - let Some(spec) = request.spec else { - return Err(anyhow!("zone spec not provided")); - }; - let uuid = Uuid::new_v4(); - self.zones - .update( - uuid, - Zone { - id: uuid.to_string(), - status: Some(ZoneStatus { - state: ZoneState::Creating.into(), - network_status: None, - exit_status: None, - error_status: None, - resource_status: None, - host: self.zlt.host_uuid().to_string(), - domid: u32::MAX, - }), - spec: Some(spec), - }, - ) - .await?; - self.zone_reconciler_notify.send(uuid).await?; - Ok(CreateZoneReply { - zone_id: uuid.to_string(), - }) - } -} diff --git a/crates/daemon/src/control/destroy_zone.rs b/crates/daemon/src/control/destroy_zone.rs deleted file mode 100644 index 1f9a14ae..00000000 --- a/crates/daemon/src/control/destroy_zone.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::str::FromStr; - -use anyhow::{anyhow, Result}; -use tokio::sync::mpsc::Sender; -use uuid::Uuid; - -use krata::v1::common::ZoneState; -use krata::v1::control::{DestroyZoneReply, DestroyZoneRequest}; - -use crate::db::zone::ZoneStore; - -pub struct DestroyZoneRpc { - zones: ZoneStore, - zone_reconciler_notify: Sender, -} - -impl DestroyZoneRpc { - pub fn new(zones: ZoneStore, zone_reconciler_notify: Sender) -> Self { - Self { - zones, - zone_reconciler_notify, - } - } - - pub async fn process(self, request: DestroyZoneRequest) -> Result { - let uuid = Uuid::from_str(&request.zone_id)?; - let Some(mut zone) = self.zones.read(uuid).await? else { - return Err(anyhow!("zone not found")); - }; - - zone.status = Some(zone.status.as_mut().cloned().unwrap_or_default()); - - if zone.status.as_ref().unwrap().state() == ZoneState::Destroyed { - return Err(anyhow!("zone already destroyed")); - } - - zone.status.as_mut().unwrap().state = ZoneState::Destroying.into(); - self.zones.update(uuid, zone).await?; - self.zone_reconciler_notify.send(uuid).await?; - Ok(DestroyZoneReply {}) - } -} diff --git a/crates/daemon/src/control/exec_inside_zone.rs b/crates/daemon/src/control/exec_inside_zone.rs deleted file mode 100644 index 003a2d7c..00000000 --- a/crates/daemon/src/control/exec_inside_zone.rs +++ /dev/null @@ -1,133 +0,0 @@ -use std::pin::Pin; -use std::str::FromStr; - -use anyhow::{anyhow, Result}; -use async_stream::try_stream; -use tokio::select; -use tokio_stream::{Stream, StreamExt}; -use tonic::{Status, Streaming}; -use uuid::Uuid; - -use krata::idm::internal::Request; -use krata::{ - idm::internal::{ - exec_stream_request_update::Update, request::Request as IdmRequestType, - response::Response as IdmResponseType, ExecEnvVar, ExecStreamRequestStart, - ExecStreamRequestStdin, ExecStreamRequestTerminalSize, ExecStreamRequestUpdate, - Request as IdmRequest, - }, - v1::control::{ExecInsideZoneReply, ExecInsideZoneRequest}, -}; - -use crate::control::ApiError; -use crate::idm::DaemonIdmHandle; - -pub struct ExecInsideZoneRpc { - idm: DaemonIdmHandle, -} - -impl ExecInsideZoneRpc { - pub fn new(idm: DaemonIdmHandle) -> Self { - Self { idm } - } - - pub async fn process( - self, - mut input: Streaming, - ) -> Result> + Send + 'static>>> - { - let Some(request) = input.next().await else { - return Err(anyhow!("expected to have at least one request")); - }; - let request = request?; - - let Some(task) = request.task else { - return Err(anyhow!("task is missing")); - }; - - let uuid = Uuid::from_str(&request.zone_id)?; - let idm = self.idm.client(uuid).await?; - - let idm_request = Request { - request: Some(IdmRequestType::ExecStream(ExecStreamRequestUpdate { - update: Some(Update::Start(ExecStreamRequestStart { - environment: task - .environment - .into_iter() - .map(|x| ExecEnvVar { - key: x.key, - value: x.value, - }) - .collect(), - command: task.command, - working_directory: task.working_directory, - tty: task.tty, - terminal_size: request.terminal_size.map(|size| { - ExecStreamRequestTerminalSize { - rows: size.rows, - columns: size.columns, - } - }), - })), - })), - }; - - let output = try_stream! { - let mut handle = idm.send_stream(idm_request).await.map_err(|x| ApiError { - message: x.to_string(), - })?; - - loop { - select! { - x = input.next() => if let Some(update) = x { - let update: Result = update.map_err(|error| ApiError { - message: error.to_string() - }.into()); - - if let Ok(update) = update { - if !update.stdin.is_empty() { - let _ = handle.update(IdmRequest { - request: Some(IdmRequestType::ExecStream(ExecStreamRequestUpdate { - update: Some(Update::Stdin(ExecStreamRequestStdin { - data: update.stdin, - closed: update.stdin_closed, - })), - }))}).await; - } - - if let Some(ref terminal_size) = update.terminal_size { - let _ = handle.update(IdmRequest { - request: Some(IdmRequestType::ExecStream(ExecStreamRequestUpdate { - update: Some(Update::TerminalResize(ExecStreamRequestTerminalSize { - rows: terminal_size.rows, - columns: terminal_size.columns, - })), - }))}).await; - } - } - }, - x = handle.receiver.recv() => match x { - Some(response) => { - let Some(IdmResponseType::ExecStream(update)) = response.response else { - break; - }; - let reply = ExecInsideZoneReply { - exited: update.exited, - error: update.error, - exit_code: update.exit_code, - stdout: update.stdout, - stderr: update.stderr, - }; - yield reply; - }, - None => { - break; - } - } - } - } - }; - - Ok(Box::pin(output)) - } -} diff --git a/crates/daemon/src/control/get_host_cpu_topology.rs b/crates/daemon/src/control/get_host_cpu_topology.rs deleted file mode 100644 index 1ec5139e..00000000 --- a/crates/daemon/src/control/get_host_cpu_topology.rs +++ /dev/null @@ -1,33 +0,0 @@ -use anyhow::Result; -use krata::v1::control::{GetHostCpuTopologyReply, GetHostCpuTopologyRequest, HostCpuTopologyInfo}; -use kratart::Runtime; - -pub struct GetHostCpuTopologyRpc { - runtime: Runtime, -} - -impl GetHostCpuTopologyRpc { - pub fn new(runtime: Runtime) -> Self { - Self { runtime } - } - - pub async fn process( - self, - _request: GetHostCpuTopologyRequest, - ) -> Result { - let power = self.runtime.power_management_context().await?; - let cpu_topology = power.cpu_topology().await?; - let mut cpus = vec![]; - - for cpu in cpu_topology { - cpus.push(HostCpuTopologyInfo { - core: cpu.core, - socket: cpu.socket, - node: cpu.node, - thread: cpu.thread, - class: cpu.class as i32, - }) - } - Ok(GetHostCpuTopologyReply { cpus }) - } -} diff --git a/crates/daemon/src/control/get_host_status.rs b/crates/daemon/src/control/get_host_status.rs deleted file mode 100644 index 40774a3b..00000000 --- a/crates/daemon/src/control/get_host_status.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::command::DaemonCommand; -use crate::network::assignment::NetworkAssignment; -use crate::zlt::ZoneLookupTable; -use anyhow::Result; -use krata::v1::control::{GetHostStatusReply, GetHostStatusRequest}; - -pub struct GetHostStatusRpc { - network: NetworkAssignment, - zlt: ZoneLookupTable, -} - -impl GetHostStatusRpc { - pub fn new(ip: NetworkAssignment, zlt: ZoneLookupTable) -> Self { - Self { network: ip, zlt } - } - - pub async fn process(self, _request: GetHostStatusRequest) -> Result { - let host_reservation = self.network.retrieve(self.zlt.host_uuid()).await?; - Ok(GetHostStatusReply { - host_domid: self.zlt.host_domid(), - host_uuid: self.zlt.host_uuid().to_string(), - krata_version: DaemonCommand::version(), - host_ipv4: host_reservation - .as_ref() - .map(|x| format!("{}/{}", x.ipv4, x.ipv4_prefix)) - .unwrap_or_default(), - host_ipv6: host_reservation - .as_ref() - .map(|x| format!("{}/{}", x.ipv6, x.ipv6_prefix)) - .unwrap_or_default(), - host_mac: host_reservation - .as_ref() - .map(|x| x.mac.to_string().to_lowercase().replace('-', ":")) - .unwrap_or_default(), - }) - } -} diff --git a/crates/daemon/src/control/get_zone.rs b/crates/daemon/src/control/get_zone.rs deleted file mode 100644 index 7fb8ad50..00000000 --- a/crates/daemon/src/control/get_zone.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::str::FromStr; - -use anyhow::Result; -use uuid::Uuid; - -use krata::v1::control::{GetZoneReply, GetZoneRequest}; - -use crate::db::zone::ZoneStore; - -pub struct GetZoneRpc { - zones: ZoneStore, -} - -impl GetZoneRpc { - pub fn new(zones: ZoneStore) -> Self { - Self { zones } - } - - pub async fn process(self, request: GetZoneRequest) -> Result { - let mut zones = self.zones.list().await?; - let zone = zones.remove(&Uuid::from_str(&request.zone_id)?); - Ok(GetZoneReply { zone }) - } -} diff --git a/crates/daemon/src/control/list_devices.rs b/crates/daemon/src/control/list_devices.rs deleted file mode 100644 index ff05f391..00000000 --- a/crates/daemon/src/control/list_devices.rs +++ /dev/null @@ -1,28 +0,0 @@ -use anyhow::Result; - -use krata::v1::control::{DeviceInfo, ListDevicesReply, ListDevicesRequest}; - -use crate::devices::DaemonDeviceManager; - -pub struct ListDevicesRpc { - devices: DaemonDeviceManager, -} - -impl ListDevicesRpc { - pub fn new(devices: DaemonDeviceManager) -> Self { - Self { devices } - } - - pub async fn process(self, _request: ListDevicesRequest) -> Result { - let mut devices = Vec::new(); - let state = self.devices.copy().await?; - for (name, state) in state { - devices.push(DeviceInfo { - name, - claimed: state.owner.is_some(), - owner: state.owner.map(|x| x.to_string()).unwrap_or_default(), - }); - } - Ok(ListDevicesReply { devices }) - } -} diff --git a/crates/daemon/src/control/list_network_reservations.rs b/crates/daemon/src/control/list_network_reservations.rs deleted file mode 100644 index 940c4692..00000000 --- a/crates/daemon/src/control/list_network_reservations.rs +++ /dev/null @@ -1,28 +0,0 @@ -use anyhow::Result; - -use krata::v1::{ - common::NetworkReservation, - control::{ListNetworkReservationsReply, ListNetworkReservationsRequest}, -}; - -use crate::network::assignment::NetworkAssignment; - -pub struct ListNetworkReservationsRpc { - network: NetworkAssignment, -} - -impl ListNetworkReservationsRpc { - pub fn new(network: NetworkAssignment) -> Self { - Self { network } - } - - pub async fn process( - self, - _request: ListNetworkReservationsRequest, - ) -> Result { - let state = self.network.read_reservations().await?; - let reservations: Vec = - state.into_values().map(|x| x.into()).collect::>(); - Ok(ListNetworkReservationsReply { reservations }) - } -} diff --git a/crates/daemon/src/control/list_zones.rs b/crates/daemon/src/control/list_zones.rs deleted file mode 100644 index cc9cb78d..00000000 --- a/crates/daemon/src/control/list_zones.rs +++ /dev/null @@ -1,21 +0,0 @@ -use anyhow::Result; -use krata::v1::common::Zone; -use krata::v1::control::{ListZonesReply, ListZonesRequest}; - -use crate::db::zone::ZoneStore; - -pub struct ListZonesRpc { - zones: ZoneStore, -} - -impl ListZonesRpc { - pub fn new(zones: ZoneStore) -> Self { - Self { zones } - } - - pub async fn process(self, _request: ListZonesRequest) -> Result { - let zones = self.zones.list().await?; - let zones = zones.into_values().collect::>(); - Ok(ListZonesReply { zones }) - } -} diff --git a/crates/daemon/src/control/mod.rs b/crates/daemon/src/control/mod.rs deleted file mode 100644 index f7ee43f9..00000000 --- a/crates/daemon/src/control/mod.rs +++ /dev/null @@ -1,365 +0,0 @@ -use std::pin::Pin; - -use anyhow::Error; -use futures::Stream; -use list_network_reservations::ListNetworkReservationsRpc; -use tokio::sync::mpsc::Sender; -use tonic::{Request, Response, Status, Streaming}; -use uuid::Uuid; - -use krata::v1::control::{ - control_service_server::ControlService, CreateZoneReply, CreateZoneRequest, DestroyZoneReply, - DestroyZoneRequest, ExecInsideZoneReply, ExecInsideZoneRequest, GetHostCpuTopologyReply, - GetHostCpuTopologyRequest, GetHostStatusReply, GetHostStatusRequest, ListDevicesReply, - ListDevicesRequest, ListZonesReply, ListZonesRequest, PullImageReply, PullImageRequest, - ReadHypervisorConsoleReply, ReadHypervisorConsoleRequest, ReadZoneMetricsReply, - ReadZoneMetricsRequest, ResolveZoneIdReply, ResolveZoneIdRequest, SnoopIdmReply, - SnoopIdmRequest, UpdateZoneResourcesReply, UpdateZoneResourcesRequest, WatchEventsReply, - WatchEventsRequest, ZoneConsoleReply, ZoneConsoleRequest, -}; -use krata::v1::control::{ - GetZoneReply, GetZoneRequest, ListNetworkReservationsReply, ListNetworkReservationsRequest, - SetHostPowerManagementPolicyReply, SetHostPowerManagementPolicyRequest, -}; -use krataoci::packer::service::OciPackerService; -use kratart::Runtime; - -use crate::control::attach_zone_console::AttachZoneConsoleRpc; -use crate::control::create_zone::CreateZoneRpc; -use crate::control::destroy_zone::DestroyZoneRpc; -use crate::control::exec_inside_zone::ExecInsideZoneRpc; -use crate::control::get_host_cpu_topology::GetHostCpuTopologyRpc; -use crate::control::get_host_status::GetHostStatusRpc; -use crate::control::get_zone::GetZoneRpc; -use crate::control::list_devices::ListDevicesRpc; -use crate::control::list_zones::ListZonesRpc; -use crate::control::pull_image::PullImageRpc; -use crate::control::read_hypervisor_console::ReadHypervisorConsoleRpc; -use crate::control::read_zone_metrics::ReadZoneMetricsRpc; -use crate::control::resolve_zone_id::ResolveZoneIdRpc; -use crate::control::set_host_power_management_policy::SetHostPowerManagementPolicyRpc; -use crate::control::snoop_idm::SnoopIdmRpc; -use crate::control::update_zone_resources::UpdateZoneResourcesRpc; -use crate::control::watch_events::WatchEventsRpc; -use crate::db::zone::ZoneStore; -use crate::network::assignment::NetworkAssignment; -use crate::{ - console::DaemonConsoleHandle, devices::DaemonDeviceManager, event::DaemonEventContext, - idm::DaemonIdmHandle, zlt::ZoneLookupTable, -}; - -pub mod attach_zone_console; -pub mod create_zone; -pub mod destroy_zone; -pub mod exec_inside_zone; -pub mod get_host_cpu_topology; -pub mod get_host_status; -pub mod get_zone; -pub mod list_devices; -pub mod list_network_reservations; -pub mod list_zones; -pub mod pull_image; -pub mod read_hypervisor_console; -pub mod read_zone_metrics; -pub mod resolve_zone_id; -pub mod set_host_power_management_policy; -pub mod snoop_idm; -pub mod update_zone_resources; -pub mod watch_events; - -pub struct ApiError { - message: String, -} - -impl From for ApiError { - fn from(value: Error) -> Self { - ApiError { - message: value.to_string(), - } - } -} - -impl From for Status { - fn from(value: ApiError) -> Self { - Status::unknown(value.message) - } -} - -#[derive(Clone)] -pub struct DaemonControlService { - zlt: ZoneLookupTable, - devices: DaemonDeviceManager, - events: DaemonEventContext, - console: DaemonConsoleHandle, - idm: DaemonIdmHandle, - zones: ZoneStore, - network: NetworkAssignment, - zone_reconciler_notify: Sender, - packer: OciPackerService, - runtime: Runtime, -} - -impl DaemonControlService { - #[allow(clippy::too_many_arguments)] - pub fn new( - zlt: ZoneLookupTable, - devices: DaemonDeviceManager, - events: DaemonEventContext, - console: DaemonConsoleHandle, - idm: DaemonIdmHandle, - zones: ZoneStore, - network: NetworkAssignment, - zone_reconciler_notify: Sender, - packer: OciPackerService, - runtime: Runtime, - ) -> Self { - Self { - zlt, - devices, - events, - console, - idm, - zones, - network, - zone_reconciler_notify, - packer, - runtime, - } - } -} - -#[tonic::async_trait] -impl ControlService for DaemonControlService { - async fn get_host_status( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - adapt( - GetHostStatusRpc::new(self.network.clone(), self.zlt.clone()) - .process(request) - .await, - ) - } - - type SnoopIdmStream = - Pin> + Send + 'static>>; - - async fn snoop_idm( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - adapt( - SnoopIdmRpc::new(self.idm.clone(), self.zlt.clone()) - .process(request) - .await, - ) - } - - async fn get_host_cpu_topology( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - adapt( - GetHostCpuTopologyRpc::new(self.runtime.clone()) - .process(request) - .await, - ) - } - - async fn set_host_power_management_policy( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - adapt( - SetHostPowerManagementPolicyRpc::new(self.runtime.clone()) - .process(request) - .await, - ) - } - - async fn list_devices( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - adapt( - ListDevicesRpc::new(self.devices.clone()) - .process(request) - .await, - ) - } - - async fn list_network_reservations( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - adapt( - ListNetworkReservationsRpc::new(self.network.clone()) - .process(request) - .await, - ) - } - - type PullImageStream = - Pin> + Send + 'static>>; - - async fn pull_image( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - adapt( - PullImageRpc::new(self.packer.clone()) - .process(request) - .await, - ) - } - - async fn create_zone( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - adapt( - CreateZoneRpc::new( - self.zones.clone(), - self.zlt.clone(), - self.zone_reconciler_notify.clone(), - ) - .process(request) - .await, - ) - } - - async fn destroy_zone( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - adapt( - DestroyZoneRpc::new(self.zones.clone(), self.zone_reconciler_notify.clone()) - .process(request) - .await, - ) - } - - async fn resolve_zone_id( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - adapt( - ResolveZoneIdRpc::new(self.zones.clone()) - .process(request) - .await, - ) - } - - async fn get_zone( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - adapt(GetZoneRpc::new(self.zones.clone()).process(request).await) - } - - async fn update_zone_resources( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - adapt( - UpdateZoneResourcesRpc::new(self.runtime.clone(), self.zones.clone()) - .process(request) - .await, - ) - } - - async fn list_zones( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - adapt(ListZonesRpc::new(self.zones.clone()).process(request).await) - } - - type AttachZoneConsoleStream = - Pin> + Send + 'static>>; - - async fn attach_zone_console( - &self, - request: Request>, - ) -> Result, Status> { - let input = request.into_inner(); - adapt( - AttachZoneConsoleRpc::new(self.console.clone()) - .process(input) - .await, - ) - } - - type ExecInsideZoneStream = - Pin> + Send + 'static>>; - - async fn exec_inside_zone( - &self, - request: Request>, - ) -> Result, Status> { - let input = request.into_inner(); - adapt( - ExecInsideZoneRpc::new(self.idm.clone()) - .process(input) - .await, - ) - } - - async fn read_zone_metrics( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - adapt( - ReadZoneMetricsRpc::new(self.idm.clone()) - .process(request) - .await, - ) - } - - type WatchEventsStream = - Pin> + Send + 'static>>; - - async fn watch_events( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - adapt( - WatchEventsRpc::new(self.events.clone()) - .process(request) - .await, - ) - } - - async fn read_hypervisor_console( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - adapt( - ReadHypervisorConsoleRpc::new(self.runtime.clone()) - .process(request) - .await, - ) - } -} - -fn adapt(result: anyhow::Result) -> Result, Status> { - result - .map(Response::new) - .map_err(|error| Status::unknown(error.to_string())) -} diff --git a/crates/daemon/src/control/pull_image.rs b/crates/daemon/src/control/pull_image.rs deleted file mode 100644 index b54cb6cc..00000000 --- a/crates/daemon/src/control/pull_image.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::control::ApiError; -use crate::oci::convert_oci_progress; -use anyhow::Result; -use async_stream::try_stream; -use krata::v1::common::OciImageFormat; -use krata::v1::control::{PullImageReply, PullImageRequest}; -use krataoci::name::ImageName; -use krataoci::packer::service::OciPackerService; -use krataoci::packer::{OciPackedFormat, OciPackedImage}; -use krataoci::progress::{OciProgress, OciProgressContext}; -use std::pin::Pin; -use tokio::select; -use tokio::task::JoinError; -use tokio_stream::Stream; -use tonic::Status; - -enum PullImageSelect { - Progress(Option), - Completed(Result, JoinError>), -} - -pub struct PullImageRpc { - packer: OciPackerService, -} - -impl PullImageRpc { - pub fn new(packer: OciPackerService) -> Self { - Self { packer } - } - - pub async fn process( - self, - request: PullImageRequest, - ) -> Result> + Send + 'static>>> { - let name = ImageName::parse(&request.image)?; - let format = match request.format() { - OciImageFormat::Unknown => OciPackedFormat::Squashfs, - OciImageFormat::Squashfs => OciPackedFormat::Squashfs, - OciImageFormat::Erofs => OciPackedFormat::Erofs, - OciImageFormat::Tar => OciPackedFormat::Tar, - }; - let (context, mut receiver) = OciProgressContext::create(); - let our_packer = self.packer; - - let output = try_stream! { - let mut task = tokio::task::spawn(async move { - our_packer.request(name, format, request.overwrite_cache, request.update, context).await - }); - let abort_handle = task.abort_handle(); - let _task_cancel_guard = scopeguard::guard(abort_handle, |handle| { - handle.abort(); - }); - - loop { - let what = select! { - x = receiver.changed() => match x { - Ok(_) => PullImageSelect::Progress(Some(receiver.borrow_and_update().clone())), - Err(_) => PullImageSelect::Progress(None), - }, - x = &mut task => PullImageSelect::Completed(x), - }; - match what { - PullImageSelect::Progress(Some(progress)) => { - let reply = PullImageReply { - progress: Some(convert_oci_progress(progress)), - digest: String::new(), - format: OciImageFormat::Unknown.into(), - }; - yield reply; - }, - - PullImageSelect::Completed(result) => { - let result = result.map_err(|err| ApiError { - message: err.to_string(), - })?; - let packed = result.map_err(|err| ApiError { - message: err.to_string(), - })?; - let reply = PullImageReply { - progress: None, - digest: packed.digest, - format: match packed.format { - OciPackedFormat::Squashfs => OciImageFormat::Squashfs.into(), - OciPackedFormat::Erofs => OciImageFormat::Erofs.into(), - OciPackedFormat::Tar => OciImageFormat::Tar.into(), - }, - }; - yield reply; - break; - }, - - _ => { - continue; - } - } - } - }; - Ok(Box::pin(output)) - } -} diff --git a/crates/daemon/src/control/read_hypervisor_console.rs b/crates/daemon/src/control/read_hypervisor_console.rs deleted file mode 100644 index ff102619..00000000 --- a/crates/daemon/src/control/read_hypervisor_console.rs +++ /dev/null @@ -1,23 +0,0 @@ -use anyhow::Result; -use krata::v1::control::{ReadHypervisorConsoleReply, ReadHypervisorConsoleRequest}; -use kratart::Runtime; - -pub struct ReadHypervisorConsoleRpc { - runtime: Runtime, -} - -impl ReadHypervisorConsoleRpc { - pub fn new(runtime: Runtime) -> Self { - Self { runtime } - } - - pub async fn process( - self, - _: ReadHypervisorConsoleRequest, - ) -> Result { - let data = self.runtime.read_hypervisor_console(false).await?; - Ok(ReadHypervisorConsoleReply { - data: data.to_string(), - }) - } -} diff --git a/crates/daemon/src/control/read_zone_metrics.rs b/crates/daemon/src/control/read_zone_metrics.rs deleted file mode 100644 index 686bdc35..00000000 --- a/crates/daemon/src/control/read_zone_metrics.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::str::FromStr; - -use anyhow::Result; -use uuid::Uuid; - -use krata::idm::internal::MetricsRequest; -use krata::idm::internal::{ - request::Request as IdmRequestType, response::Response as IdmResponseType, - Request as IdmRequest, -}; -use krata::v1::control::{ReadZoneMetricsReply, ReadZoneMetricsRequest}; - -use crate::idm::DaemonIdmHandle; -use crate::metrics::idm_metric_to_api; - -pub struct ReadZoneMetricsRpc { - idm: DaemonIdmHandle, -} - -impl ReadZoneMetricsRpc { - pub fn new(idm: DaemonIdmHandle) -> Self { - Self { idm } - } - - pub async fn process(self, request: ReadZoneMetricsRequest) -> Result { - let uuid = Uuid::from_str(&request.zone_id)?; - let client = self.idm.client(uuid).await?; - let response = client - .send(IdmRequest { - request: Some(IdmRequestType::Metrics(MetricsRequest {})), - }) - .await?; - - let mut reply = ReadZoneMetricsReply::default(); - if let Some(IdmResponseType::Metrics(metrics)) = response.response { - reply.root = metrics.root.map(idm_metric_to_api); - } - Ok(reply) - } -} diff --git a/crates/daemon/src/control/resolve_zone_id.rs b/crates/daemon/src/control/resolve_zone_id.rs deleted file mode 100644 index e2540ea9..00000000 --- a/crates/daemon/src/control/resolve_zone_id.rs +++ /dev/null @@ -1,30 +0,0 @@ -use anyhow::Result; -use krata::v1::common::Zone; -use krata::v1::control::{ResolveZoneIdReply, ResolveZoneIdRequest}; - -use crate::db::zone::ZoneStore; - -pub struct ResolveZoneIdRpc { - zones: ZoneStore, -} - -impl ResolveZoneIdRpc { - pub fn new(zones: ZoneStore) -> Self { - Self { zones } - } - - pub async fn process(self, request: ResolveZoneIdRequest) -> Result { - let zones = self.zones.list().await?; - let zones = zones - .into_values() - .filter(|x| { - let comparison_spec = x.spec.as_ref().cloned().unwrap_or_default(); - (!request.name.is_empty() && comparison_spec.name == request.name) - || x.id == request.name - }) - .collect::>(); - Ok(ResolveZoneIdReply { - zone_id: zones.first().cloned().map(|x| x.id).unwrap_or_default(), - }) - } -} diff --git a/crates/daemon/src/control/set_host_power_management_policy.rs b/crates/daemon/src/control/set_host_power_management_policy.rs deleted file mode 100644 index aeff1098..00000000 --- a/crates/daemon/src/control/set_host_power_management_policy.rs +++ /dev/null @@ -1,25 +0,0 @@ -use anyhow::Result; -use krata::v1::control::{SetHostPowerManagementPolicyReply, SetHostPowerManagementPolicyRequest}; -use kratart::Runtime; - -pub struct SetHostPowerManagementPolicyRpc { - runtime: Runtime, -} - -impl SetHostPowerManagementPolicyRpc { - pub fn new(runtime: Runtime) -> Self { - Self { runtime } - } - - pub async fn process( - self, - request: SetHostPowerManagementPolicyRequest, - ) -> Result { - let power = self.runtime.power_management_context().await?; - let scheduler = &request.scheduler; - - power.set_smt_policy(request.smt_awareness).await?; - power.set_scheduler_policy(scheduler).await?; - Ok(SetHostPowerManagementPolicyReply {}) - } -} diff --git a/crates/daemon/src/control/snoop_idm.rs b/crates/daemon/src/control/snoop_idm.rs deleted file mode 100644 index c48d54dd..00000000 --- a/crates/daemon/src/control/snoop_idm.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::idm::DaemonIdmHandle; -use crate::zlt::ZoneLookupTable; -use anyhow::Result; -use async_stream::try_stream; -use krata::v1::control::{SnoopIdmReply, SnoopIdmRequest}; -use std::pin::Pin; -use tokio_stream::Stream; -use tonic::Status; - -pub struct SnoopIdmRpc { - idm: DaemonIdmHandle, - zlt: ZoneLookupTable, -} - -impl SnoopIdmRpc { - pub fn new(idm: DaemonIdmHandle, zlt: ZoneLookupTable) -> Self { - Self { idm, zlt } - } - - pub async fn process( - self, - _request: SnoopIdmRequest, - ) -> Result> + Send + 'static>>> { - let mut messages = self.idm.snoop(); - let zlt = self.zlt.clone(); - let output = try_stream! { - while let Ok(event) = messages.recv().await { - let Some(from_uuid) = zlt.lookup_uuid_by_domid(event.from).await else { - continue; - }; - let Some(to_uuid) = zlt.lookup_uuid_by_domid(event.to).await else { - continue; - }; - yield SnoopIdmReply { from: from_uuid.to_string(), to: to_uuid.to_string(), packet: Some(event.packet) }; - } - }; - Ok(Box::pin(output)) - } -} diff --git a/crates/daemon/src/control/update_zone_resources.rs b/crates/daemon/src/control/update_zone_resources.rs deleted file mode 100644 index 74385a9d..00000000 --- a/crates/daemon/src/control/update_zone_resources.rs +++ /dev/null @@ -1,82 +0,0 @@ -use std::str::FromStr; - -use anyhow::{anyhow, Result}; -use uuid::Uuid; - -use krata::v1::common::{ZoneResourceStatus, ZoneState}; -use krata::v1::control::{UpdateZoneResourcesReply, UpdateZoneResourcesRequest}; -use kratart::Runtime; - -use crate::db::zone::ZoneStore; - -pub struct UpdateZoneResourcesRpc { - runtime: Runtime, - zones: ZoneStore, -} - -impl UpdateZoneResourcesRpc { - pub fn new(runtime: Runtime, zones: ZoneStore) -> Self { - Self { runtime, zones } - } - - pub async fn process( - self, - request: UpdateZoneResourcesRequest, - ) -> Result { - let uuid = Uuid::from_str(&request.zone_id)?; - let Some(mut zone) = self.zones.read(uuid).await? else { - return Err(anyhow!("zone not found")); - }; - - let Some(ref mut status) = zone.status else { - return Err(anyhow!("zone state not available")); - }; - - if status.state() != ZoneState::Created { - return Err(anyhow!("zone is in an invalid state")); - } - - if status.domid == 0 || status.domid == u32::MAX { - return Err(anyhow!("zone domid is invalid")); - } - - let mut resources = request.resources.unwrap_or_default(); - if resources.target_memory > resources.max_memory { - resources.max_memory = resources.target_memory; - } - - if resources.target_cpus < 1 { - resources.target_cpus = 1; - } - - let initial_resources = zone - .spec - .clone() - .unwrap_or_default() - .initial_resources - .unwrap_or_default(); - if resources.target_cpus > initial_resources.max_cpus { - resources.target_cpus = initial_resources.max_cpus; - } - resources.max_cpus = initial_resources.max_cpus; - - self.runtime - .set_memory_resources( - status.domid, - resources.target_memory * 1024 * 1024, - resources.max_memory * 1024 * 1024, - ) - .await - .map_err(|error| anyhow!("failed to set memory resources: {}", error))?; - self.runtime - .set_cpu_resources(status.domid, resources.target_cpus) - .await - .map_err(|error| anyhow!("failed to set cpu resources: {}", error))?; - status.resource_status = Some(ZoneResourceStatus { - active_resources: Some(resources), - }); - - self.zones.update(uuid, zone).await?; - Ok(UpdateZoneResourcesReply {}) - } -} diff --git a/crates/daemon/src/control/watch_events.rs b/crates/daemon/src/control/watch_events.rs deleted file mode 100644 index c1438de4..00000000 --- a/crates/daemon/src/control/watch_events.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::event::DaemonEventContext; -use anyhow::Result; -use async_stream::try_stream; -use krata::v1::control::{WatchEventsReply, WatchEventsRequest}; -use std::pin::Pin; -use tokio_stream::Stream; -use tonic::Status; - -pub struct WatchEventsRpc { - events: DaemonEventContext, -} - -impl WatchEventsRpc { - pub fn new(events: DaemonEventContext) -> Self { - Self { events } - } - - pub async fn process( - self, - _request: WatchEventsRequest, - ) -> Result> + Send + 'static>>> - { - let mut events = self.events.subscribe(); - let output = try_stream! { - while let Ok(event) = events.recv().await { - yield WatchEventsReply { event: Some(event), }; - } - }; - Ok(Box::pin(output)) - } -} diff --git a/crates/daemon/src/db/mod.rs b/crates/daemon/src/db/mod.rs deleted file mode 100644 index 3fa509ec..00000000 --- a/crates/daemon/src/db/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -use anyhow::Result; -use redb::Database; -use std::path::Path; -use std::sync::Arc; - -pub mod network; -pub mod zone; - -#[derive(Clone)] -pub struct KrataDatabase { - pub database: Arc, -} - -impl KrataDatabase { - pub fn open(path: &Path) -> Result { - let database = Database::create(path)?; - Ok(KrataDatabase { - database: Arc::new(database), - }) - } -} diff --git a/crates/daemon/src/db/network.rs b/crates/daemon/src/db/network.rs deleted file mode 100644 index fad726ac..00000000 --- a/crates/daemon/src/db/network.rs +++ /dev/null @@ -1,134 +0,0 @@ -use crate::db::KrataDatabase; -use advmac::MacAddr6; -use anyhow::Result; -use krata::v1::common::NetworkReservation as ApiNetworkReservation; -use log::error; -use redb::{ReadableTable, TableDefinition}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::net::{Ipv4Addr, Ipv6Addr}; -use uuid::Uuid; - -const NETWORK_RESERVATION_TABLE: TableDefinition = - TableDefinition::new("network-reservation"); - -#[derive(Clone)] -pub struct NetworkReservationStore { - db: KrataDatabase, -} - -impl NetworkReservationStore { - pub fn open(db: KrataDatabase) -> Result { - let write = db.database.begin_write()?; - let _ = write.open_table(NETWORK_RESERVATION_TABLE); - write.commit()?; - Ok(NetworkReservationStore { db }) - } - - pub async fn read(&self, id: Uuid) -> Result> { - let read = self.db.database.begin_read()?; - let table = read.open_table(NETWORK_RESERVATION_TABLE)?; - let Some(entry) = table.get(id.to_u128_le())? else { - return Ok(None); - }; - let bytes = entry.value(); - Ok(Some(serde_json::from_slice(bytes)?)) - } - - pub async fn list(&self) -> Result> { - enum ListEntry { - Valid(Uuid, NetworkReservation), - Invalid(Uuid), - } - let mut reservations: HashMap = HashMap::new(); - - let corruptions = { - let read = self.db.database.begin_read()?; - let table = read.open_table(NETWORK_RESERVATION_TABLE)?; - table - .iter()? - .flat_map(|result| { - result.map(|(key, value)| { - let uuid = Uuid::from_u128_le(key.value()); - match serde_json::from_slice::(value.value()) { - Ok(reservation) => ListEntry::Valid(uuid, reservation), - Err(error) => { - error!( - "found invalid network reservation in database for uuid {}: {}", - uuid, error - ); - ListEntry::Invalid(uuid) - } - } - }) - }) - .filter_map(|entry| match entry { - ListEntry::Valid(uuid, reservation) => { - reservations.insert(uuid, reservation); - None - } - - ListEntry::Invalid(uuid) => Some(uuid), - }) - .collect::>() - }; - - if !corruptions.is_empty() { - let write = self.db.database.begin_write()?; - let mut table = write.open_table(NETWORK_RESERVATION_TABLE)?; - for corruption in corruptions { - table.remove(corruption.to_u128_le())?; - } - } - - Ok(reservations) - } - - pub async fn update(&self, id: Uuid, entry: NetworkReservation) -> Result<()> { - let write = self.db.database.begin_write()?; - { - let mut table = write.open_table(NETWORK_RESERVATION_TABLE)?; - let bytes = serde_json::to_vec(&entry)?; - table.insert(id.to_u128_le(), bytes.as_slice())?; - } - write.commit()?; - Ok(()) - } - - pub async fn remove(&self, id: Uuid) -> Result<()> { - let write = self.db.database.begin_write()?; - { - let mut table = write.open_table(NETWORK_RESERVATION_TABLE)?; - table.remove(id.to_u128_le())?; - } - write.commit()?; - Ok(()) - } -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct NetworkReservation { - pub uuid: String, - pub ipv4: Ipv4Addr, - pub ipv6: Ipv6Addr, - pub mac: MacAddr6, - pub ipv4_prefix: u8, - pub ipv6_prefix: u8, - pub gateway_ipv4: Ipv4Addr, - pub gateway_ipv6: Ipv6Addr, - pub gateway_mac: MacAddr6, -} - -impl From for ApiNetworkReservation { - fn from(val: NetworkReservation) -> Self { - ApiNetworkReservation { - uuid: val.uuid, - ipv4: format!("{}/{}", val.ipv4, val.ipv4_prefix), - ipv6: format!("{}/{}", val.ipv6, val.ipv6_prefix), - mac: val.mac.to_string().to_lowercase().replace('-', ":"), - gateway_ipv4: format!("{}/{}", val.gateway_ipv4, val.ipv4_prefix), - gateway_ipv6: format!("{}/{}", val.gateway_ipv6, val.ipv6_prefix), - gateway_mac: val.gateway_mac.to_string().to_lowercase().replace('-', ":"), - } - } -} diff --git a/crates/daemon/src/db/zone.rs b/crates/daemon/src/db/zone.rs deleted file mode 100644 index e56347e5..00000000 --- a/crates/daemon/src/db/zone.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::collections::HashMap; - -use crate::db::KrataDatabase; -use anyhow::Result; -use krata::v1::common::Zone; -use log::error; -use prost::Message; -use redb::{ReadableTable, TableDefinition}; -use uuid::Uuid; - -const ZONE_TABLE: TableDefinition = TableDefinition::new("zone"); - -#[derive(Clone)] -pub struct ZoneStore { - db: KrataDatabase, -} - -impl ZoneStore { - pub fn open(db: KrataDatabase) -> Result { - let write = db.database.begin_write()?; - let _ = write.open_table(ZONE_TABLE); - write.commit()?; - Ok(ZoneStore { db }) - } - - pub async fn read(&self, id: Uuid) -> Result> { - let read = self.db.database.begin_read()?; - let table = read.open_table(ZONE_TABLE)?; - let Some(entry) = table.get(id.to_u128_le())? else { - return Ok(None); - }; - let bytes = entry.value(); - Ok(Some(Zone::decode(bytes)?)) - } - - pub async fn list(&self) -> Result> { - let mut zones: HashMap = HashMap::new(); - let read = self.db.database.begin_read()?; - let table = read.open_table(ZONE_TABLE)?; - for result in table.iter()? { - let (key, value) = result?; - let uuid = Uuid::from_u128_le(key.value()); - let state = match Zone::decode(value.value()) { - Ok(state) => state, - Err(error) => { - error!( - "found invalid zone state in database for uuid {}: {}", - uuid, error - ); - continue; - } - }; - zones.insert(uuid, state); - } - Ok(zones) - } - - pub async fn update(&self, id: Uuid, entry: Zone) -> Result<()> { - let write = self.db.database.begin_write()?; - { - let mut table = write.open_table(ZONE_TABLE)?; - let bytes = entry.encode_to_vec(); - table.insert(id.to_u128_le(), bytes.as_slice())?; - } - write.commit()?; - Ok(()) - } - - pub async fn remove(&self, id: Uuid) -> Result<()> { - let write = self.db.database.begin_write()?; - { - let mut table = write.open_table(ZONE_TABLE)?; - table.remove(id.to_u128_le())?; - } - write.commit()?; - Ok(()) - } -} diff --git a/crates/daemon/src/devices.rs b/crates/daemon/src/devices.rs deleted file mode 100644 index 2d4cc433..00000000 --- a/crates/daemon/src/devices.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::{collections::HashMap, sync::Arc}; - -use anyhow::{anyhow, Result}; -use log::warn; -use tokio::sync::RwLock; -use uuid::Uuid; - -use crate::config::{DaemonConfig, DaemonPciDeviceConfig}; - -#[derive(Clone)] -pub struct DaemonDeviceState { - pub pci: Option, - pub owner: Option, -} - -#[derive(Clone)] -pub struct DaemonDeviceManager { - config: Arc, - devices: Arc>>, -} - -impl DaemonDeviceManager { - pub fn new(config: Arc) -> Self { - Self { - config, - devices: Arc::new(RwLock::new(HashMap::new())), - } - } - - pub async fn claim(&self, device: &str, uuid: Uuid) -> Result { - let mut devices = self.devices.write().await; - let Some(state) = devices.get_mut(device) else { - return Err(anyhow!( - "unable to claim unknown device '{}' for zone {}", - device, - uuid - )); - }; - - if let Some(owner) = state.owner { - return Err(anyhow!( - "unable to claim device '{}' for zone {}: already claimed by {}", - device, - uuid, - owner - )); - } - - state.owner = Some(uuid); - Ok(state.clone()) - } - - pub async fn release_all(&self, uuid: Uuid) -> Result<()> { - let mut devices = self.devices.write().await; - for state in (*devices).values_mut() { - if state.owner == Some(uuid) { - state.owner = None; - } - } - Ok(()) - } - - pub async fn release(&self, device: &str, uuid: Uuid) -> Result<()> { - let mut devices = self.devices.write().await; - let Some(state) = devices.get_mut(device) else { - return Ok(()); - }; - - if let Some(owner) = state.owner { - if owner != uuid { - return Ok(()); - } - } - - state.owner = None; - Ok(()) - } - - pub async fn update_claims(&self, claims: HashMap) -> Result<()> { - let mut devices = self.devices.write().await; - devices.clear(); - for (name, pci) in &self.config.pci.devices { - let owner = claims.get(name).cloned(); - devices.insert( - name.clone(), - DaemonDeviceState { - owner, - pci: Some(pci.clone()), - }, - ); - } - - for (name, uuid) in &claims { - if !devices.contains_key(name) { - warn!("unknown device '{}' assigned to zone {}", name, uuid); - } - } - - Ok(()) - } - - pub async fn copy(&self) -> Result> { - let devices = self.devices.read().await; - Ok(devices.clone()) - } -} diff --git a/crates/daemon/src/event.rs b/crates/daemon/src/event.rs deleted file mode 100644 index 781ec722..00000000 --- a/crates/daemon/src/event.rs +++ /dev/null @@ -1,185 +0,0 @@ -use std::{ - collections::{hash_map::Entry, HashMap}, - str::FromStr, - time::Duration, -}; - -use crate::db::zone::ZoneStore; -use crate::idm::DaemonIdmHandle; -use anyhow::Result; -use krata::v1::common::ZoneExitStatus; -use krata::{ - idm::{internal::event::Event as EventType, internal::Event}, - v1::common::{ZoneState, ZoneStatus}, -}; -use log::{error, warn}; -use tokio::{ - select, - sync::{ - broadcast, - mpsc::{channel, Receiver, Sender}, - }, - task::JoinHandle, - time, -}; -use uuid::Uuid; - -pub type DaemonEvent = krata::v1::control::watch_events_reply::Event; - -const EVENT_CHANNEL_QUEUE_LEN: usize = 1000; -const IDM_EVENT_CHANNEL_QUEUE_LEN: usize = 1000; - -#[derive(Clone)] -pub struct DaemonEventContext { - sender: broadcast::Sender, -} - -impl DaemonEventContext { - pub fn subscribe(&self) -> broadcast::Receiver { - self.sender.subscribe() - } - - pub fn send(&self, event: DaemonEvent) -> Result<()> { - let _ = self.sender.send(event); - Ok(()) - } -} - -pub struct DaemonEventGenerator { - zones: ZoneStore, - zone_reconciler_notify: Sender, - feed: broadcast::Receiver, - idm: DaemonIdmHandle, - idms: HashMap)>, - idm_sender: Sender<(u32, Event)>, - idm_receiver: Receiver<(u32, Event)>, - _event_sender: broadcast::Sender, -} - -impl DaemonEventGenerator { - pub async fn new( - zones: ZoneStore, - zone_reconciler_notify: Sender, - idm: DaemonIdmHandle, - ) -> Result<(DaemonEventContext, DaemonEventGenerator)> { - let (sender, _) = broadcast::channel(EVENT_CHANNEL_QUEUE_LEN); - let (idm_sender, idm_receiver) = channel(IDM_EVENT_CHANNEL_QUEUE_LEN); - let generator = DaemonEventGenerator { - zones, - zone_reconciler_notify, - feed: sender.subscribe(), - idm, - idms: HashMap::new(), - idm_sender, - idm_receiver, - _event_sender: sender.clone(), - }; - let context = DaemonEventContext { sender }; - Ok((context, generator)) - } - - async fn handle_feed_event(&mut self, event: &DaemonEvent) -> Result<()> { - let DaemonEvent::ZoneChanged(changed) = event; - let Some(ref zone) = changed.zone else { - return Ok(()); - }; - - let Some(ref status) = zone.status else { - return Ok(()); - }; - - let state = status.state(); - let id = Uuid::from_str(&zone.id)?; - let domid = status.domid; - match state { - ZoneState::Created => { - if let Entry::Vacant(e) = self.idms.entry(domid) { - let client = self.idm.client_by_domid(domid).await?; - let mut receiver = client.subscribe().await?; - let sender = self.idm_sender.clone(); - let task = tokio::task::spawn(async move { - loop { - let Ok(event) = receiver.recv().await else { - break; - }; - - if let Err(error) = sender.send((domid, event)).await { - warn!("unable to deliver idm event: {}", error); - } - } - }); - e.insert((id, task)); - } - } - - ZoneState::Destroyed => { - if let Some((_, handle)) = self.idms.remove(&domid) { - handle.abort(); - } - } - - _ => {} - } - Ok(()) - } - - async fn handle_idm_event(&mut self, id: Uuid, event: Event) -> Result<()> { - match event.event { - Some(EventType::Exit(exit)) => self.handle_exit_code(id, exit.code).await, - None => Ok(()), - } - } - - async fn handle_exit_code(&mut self, id: Uuid, code: i32) -> Result<()> { - if let Some(mut zone) = self.zones.read(id).await? { - zone.status = Some(ZoneStatus { - state: ZoneState::Exited.into(), - network_status: zone.status.clone().unwrap_or_default().network_status, - exit_status: Some(ZoneExitStatus { code }), - error_status: None, - resource_status: zone.status.clone().unwrap_or_default().resource_status, - host: zone.status.clone().map(|x| x.host).unwrap_or_default(), - domid: zone.status.clone().map(|x| x.domid).unwrap_or(u32::MAX), - }); - - self.zones.update(id, zone).await?; - self.zone_reconciler_notify.send(id).await?; - } - Ok(()) - } - - async fn evaluate(&mut self) -> Result<()> { - select! { - x = self.idm_receiver.recv() => match x { - Some((domid, event)) => { - if let Some((id, _)) = self.idms.get(&domid) { - self.handle_idm_event(*id, event).await?; - } - Ok(()) - }, - None => { - Ok(()) - } - }, - x = self.feed.recv() => match x { - Ok(event) => { - self.handle_feed_event(&event).await - }, - Err(error) => { - Err(error.into()) - } - }, - } - } - - pub async fn launch(mut self) -> Result> { - Ok(tokio::task::spawn(async move { - loop { - if let Err(error) = self.evaluate().await { - error!("failed to evaluate daemon events: {}", error); - time::sleep(Duration::from_secs(5)).await; - } - } - })) - } -} diff --git a/crates/daemon/src/idm.rs b/crates/daemon/src/idm.rs deleted file mode 100644 index 05e2637b..00000000 --- a/crates/daemon/src/idm.rs +++ /dev/null @@ -1,305 +0,0 @@ -use std::{ - collections::{hash_map::Entry, HashMap}, - sync::Arc, -}; - -use anyhow::{anyhow, Result}; -use bytes::{Buf, BytesMut}; -use krata::idm::{ - client::{IdmBackend, IdmInternalClient}, - internal::INTERNAL_IDM_CHANNEL, - transport::IdmTransportPacket, -}; -use kratart::channel::ChannelService; -use log::{debug, error, warn}; -use prost::Message; -use tokio::{ - select, - sync::{ - broadcast, - mpsc::{channel, Receiver, Sender}, - Mutex, - }, - task::JoinHandle, -}; -use uuid::Uuid; - -use crate::zlt::ZoneLookupTable; - -type BackendFeedMap = Arc>>>; -type ClientMap = Arc>>; - -#[derive(Clone)] -pub struct DaemonIdmHandle { - zlt: ZoneLookupTable, - clients: ClientMap, - feeds: BackendFeedMap, - tx_sender: Sender<(u32, IdmTransportPacket)>, - task: Arc>, - snoop_sender: broadcast::Sender, -} - -impl DaemonIdmHandle { - pub fn snoop(&self) -> broadcast::Receiver { - self.snoop_sender.subscribe() - } - - pub async fn client(&self, uuid: Uuid) -> Result { - let Some(domid) = self.zlt.lookup_domid_by_uuid(&uuid).await else { - return Err(anyhow!("unable to find domain {}", uuid)); - }; - self.client_by_domid(domid).await - } - - pub async fn client_by_domid(&self, domid: u32) -> Result { - client_or_create(domid, &self.tx_sender, &self.clients, &self.feeds).await - } -} - -impl Drop for DaemonIdmHandle { - fn drop(&mut self) { - if Arc::strong_count(&self.task) <= 1 { - self.task.abort(); - } - } -} - -#[derive(Clone)] -pub struct DaemonIdmSnoopPacket { - pub from: u32, - pub to: u32, - pub packet: IdmTransportPacket, -} - -pub struct DaemonIdm { - zlt: ZoneLookupTable, - clients: ClientMap, - feeds: BackendFeedMap, - tx_sender: Sender<(u32, IdmTransportPacket)>, - tx_raw_sender: Sender<(u32, Vec)>, - tx_receiver: Receiver<(u32, IdmTransportPacket)>, - rx_receiver: Receiver<(u32, Option>)>, - snoop_sender: broadcast::Sender, - task: JoinHandle<()>, -} - -impl DaemonIdm { - pub async fn new(zlt: ZoneLookupTable) -> Result { - debug!("allocating channel service for idm"); - let (service, tx_raw_sender, rx_receiver) = - ChannelService::new("krata-channel".to_string(), None).await?; - let (tx_sender, tx_receiver) = channel(100); - let (snoop_sender, _) = broadcast::channel(100); - - debug!("starting idm channel service"); - let task = service.launch().await?; - - let clients = Arc::new(Mutex::new(HashMap::new())); - let feeds = Arc::new(Mutex::new(HashMap::new())); - - Ok(DaemonIdm { - zlt, - rx_receiver, - tx_receiver, - tx_sender, - tx_raw_sender, - snoop_sender, - task, - clients, - feeds, - }) - } - - pub async fn launch(mut self) -> Result { - let zlt = self.zlt.clone(); - let clients = self.clients.clone(); - let feeds = self.feeds.clone(); - let tx_sender = self.tx_sender.clone(); - let snoop_sender = self.snoop_sender.clone(); - let task = tokio::task::spawn(async move { - let mut buffers: HashMap = HashMap::new(); - - while let Err(error) = self.process(&mut buffers).await { - error!("failed to process idm: {}", error); - } - }); - Ok(DaemonIdmHandle { - zlt, - clients, - feeds, - tx_sender, - snoop_sender, - task: Arc::new(task), - }) - } - - async fn process_rx_packet( - &mut self, - domid: u32, - data: Option>, - buffers: &mut HashMap, - ) -> Result<()> { - // check if data is present, if it is not, that signals a closed channel. - if let Some(data) = data { - let buffer = buffers.entry(domid).or_insert_with_key(|_| BytesMut::new()); - buffer.extend_from_slice(&data); - loop { - // check if the buffer is less than the header size, if so, wait for more data - if buffer.len() < 6 { - break; - } - - // check for the magic bytes 0xff, 0xff at the start of the message, if that doesn't - // exist, clear the buffer. this ensures that partial messages won't be processed. - if buffer[0] != 0xff || buffer[1] != 0xff { - buffer.clear(); - return Ok(()); - } - - // read the size from the buffer as a little endian u32 - let size = (buffer[2] as u32 - | (buffer[3] as u32) << 8 - | (buffer[4] as u32) << 16 - | (buffer[5] as u32) << 24) as usize; - let needed = size + 6; - if buffer.len() < needed { - return Ok(()); - } - let mut packet = buffer.split_to(needed); - // advance the buffer by the header, leaving only the raw data. - packet.advance(6); - match IdmTransportPacket::decode(packet) { - Ok(packet) => { - let _ = - client_or_create(domid, &self.tx_sender, &self.clients, &self.feeds) - .await?; - let guard = self.feeds.lock().await; - if let Some(feed) = guard.get(&domid) { - let _ = feed.try_send(packet.clone()); - } - let _ = self.snoop_sender.send(DaemonIdmSnoopPacket { - from: domid, - to: 0, - packet, - }); - } - - Err(packet) => { - warn!("received invalid packet from domain {}: {}", domid, packet); - } - } - } - } else { - let mut clients = self.clients.lock().await; - let mut feeds = self.feeds.lock().await; - clients.remove(&domid); - feeds.remove(&domid); - } - Ok(()) - } - - async fn tx_packet(&mut self, domid: u32, packet: IdmTransportPacket) -> Result<()> { - let data = packet.encode_to_vec(); - let mut buffer = vec![0u8; 6]; - let length = data.len() as u32; - // magic bytes - buffer[0] = 0xff; - buffer[1] = 0xff; - // little endian u32 for message size - buffer[2] = length as u8; - buffer[3] = (length << 8) as u8; - buffer[4] = (length << 16) as u8; - buffer[5] = (length << 24) as u8; - buffer.extend_from_slice(&data); - self.tx_raw_sender.send((domid, buffer)).await?; - let _ = self.snoop_sender.send(DaemonIdmSnoopPacket { - from: 0, - to: domid, - packet, - }); - Ok(()) - } - - async fn process(&mut self, buffers: &mut HashMap) -> Result<()> { - loop { - select! { - x = self.rx_receiver.recv() => match x { - Some((domid, data)) => { - self.process_rx_packet(domid, data, buffers).await?; - }, - - None => { - break; - } - }, - x = self.tx_receiver.recv() => match x { - Some((domid, packet)) => { - self.tx_packet(domid, packet).await?; - }, - - None => { - break; - } - } - } - } - Ok(()) - } -} - -impl Drop for DaemonIdm { - fn drop(&mut self) { - self.task.abort(); - } -} - -async fn client_or_create( - domid: u32, - tx_sender: &Sender<(u32, IdmTransportPacket)>, - clients: &ClientMap, - feeds: &BackendFeedMap, -) -> Result { - let mut clients = clients.lock().await; - let mut feeds = feeds.lock().await; - match clients.entry(domid) { - Entry::Occupied(entry) => Ok(entry.get().clone()), - Entry::Vacant(entry) => { - let (rx_sender, rx_receiver) = channel(100); - feeds.insert(domid, rx_sender); - let backend = IdmDaemonBackend { - domid, - rx_receiver, - tx_sender: tx_sender.clone(), - }; - let client = IdmInternalClient::new( - INTERNAL_IDM_CHANNEL, - Box::new(backend) as Box, - ) - .await?; - entry.insert(client.clone()); - Ok(client) - } - } -} - -pub struct IdmDaemonBackend { - domid: u32, - rx_receiver: Receiver, - tx_sender: Sender<(u32, IdmTransportPacket)>, -} - -#[async_trait::async_trait] -impl IdmBackend for IdmDaemonBackend { - async fn recv(&mut self) -> Result> { - if let Some(packet) = self.rx_receiver.recv().await { - Ok(vec![packet]) - } else { - Err(anyhow!("idm receive channel closed")) - } - } - - async fn send(&mut self, packet: IdmTransportPacket) -> Result<()> { - self.tx_sender.send((self.domid, packet)).await?; - Ok(()) - } -} diff --git a/crates/daemon/src/lib.rs b/crates/daemon/src/lib.rs deleted file mode 100644 index 56c81adc..00000000 --- a/crates/daemon/src/lib.rs +++ /dev/null @@ -1,269 +0,0 @@ -use crate::db::network::NetworkReservationStore; -use crate::db::zone::ZoneStore; -use crate::db::KrataDatabase; -use crate::network::assignment::NetworkAssignment; -use anyhow::{anyhow, Result}; -use config::DaemonConfig; -use console::{DaemonConsole, DaemonConsoleHandle}; -use control::DaemonControlService; -use devices::DaemonDeviceManager; -use event::{DaemonEventContext, DaemonEventGenerator}; -use idm::{DaemonIdm, DaemonIdmHandle}; -use ipnetwork::{Ipv4Network, Ipv6Network}; -use krata::{dial::ControlDialAddress, v1::control::control_service_server::ControlServiceServer}; -use krataoci::{packer::service::OciPackerService, registry::OciPlatform}; -use kratart::Runtime; -use log::{debug, info}; -use reconcile::zone::ZoneReconciler; -use std::path::Path; -use std::time::Duration; -use std::{net::SocketAddr, path::PathBuf, str::FromStr, sync::Arc}; -use tokio::{ - fs, - net::UnixListener, - sync::mpsc::{channel, Sender}, - task::JoinHandle, -}; -use tokio_stream::wrappers::UnixListenerStream; -use tonic::transport::{Identity, Server, ServerTlsConfig}; -use uuid::Uuid; -use zlt::ZoneLookupTable; - -pub mod command; -pub mod config; -pub mod console; -pub mod control; -pub mod db; -pub mod devices; -pub mod event; -pub mod idm; -pub mod metrics; -pub mod network; -pub mod oci; -pub mod reconcile; -pub mod zlt; - -pub struct Daemon { - store: String, - _config: Arc, - zlt: ZoneLookupTable, - devices: DaemonDeviceManager, - zones: ZoneStore, - network: NetworkAssignment, - events: DaemonEventContext, - zone_reconciler_task: JoinHandle<()>, - zone_reconciler_notify: Sender, - generator_task: JoinHandle<()>, - idm: DaemonIdmHandle, - console: DaemonConsoleHandle, - packer: OciPackerService, - runtime: Runtime, -} - -const ZONE_RECONCILER_QUEUE_LEN: usize = 1000; - -impl Daemon { - pub async fn new(store: String) -> Result { - let store_dir = PathBuf::from(store.clone()); - debug!("loading configuration"); - let mut config_path = store_dir.clone(); - config_path.push("config.toml"); - - let config = DaemonConfig::load(&config_path).await?; - let config = Arc::new(config); - debug!("initializing device manager"); - let devices = DaemonDeviceManager::new(config.clone()); - - debug!("validating image cache directory"); - let mut image_cache_dir = store_dir.clone(); - image_cache_dir.push("cache"); - image_cache_dir.push("image"); - fs::create_dir_all(&image_cache_dir).await?; - - debug!("loading zone0 uuid"); - let mut host_uuid_path = store_dir.clone(); - host_uuid_path.push("host.uuid"); - let host_uuid = if host_uuid_path.is_file() { - let content = fs::read_to_string(&host_uuid_path).await?; - Uuid::from_str(content.trim()).ok() - } else { - None - }; - - let host_uuid = if let Some(host_uuid) = host_uuid { - host_uuid - } else { - let generated = Uuid::new_v4(); - let mut string = generated.to_string(); - string.push('\n'); - fs::write(&host_uuid_path, string).await?; - generated - }; - - debug!("validating zone asset directories"); - let initrd_path = detect_zone_path(&store, "initrd")?; - let kernel_path = detect_zone_path(&store, "kernel")?; - let addons_path = detect_zone_path(&store, "addons.squashfs")?; - - debug!("initializing caches and hydrating zone state"); - let seed = config.oci.seed.clone().map(PathBuf::from); - let packer = OciPackerService::new(seed, &image_cache_dir, OciPlatform::current()).await?; - debug!("initializing core runtime"); - let runtime = Runtime::new().await?; - let zlt = ZoneLookupTable::new(0, host_uuid); - let db_path = format!("{}/krata.db", store); - let database = KrataDatabase::open(Path::new(&db_path))?; - let zones = ZoneStore::open(database.clone())?; - let (zone_reconciler_notify, zone_reconciler_receiver) = - channel::(ZONE_RECONCILER_QUEUE_LEN); - debug!("starting IDM service"); - let idm = DaemonIdm::new(zlt.clone()).await?; - let idm = idm.launch().await?; - debug!("initializing console interfaces"); - let console = DaemonConsole::new(zlt.clone()).await?; - let console = console.launch().await?; - let (events, generator) = - DaemonEventGenerator::new(zones.clone(), zone_reconciler_notify.clone(), idm.clone()) - .await?; - let runtime_for_reconciler = runtime.dupe().await?; - let ipv4_network = Ipv4Network::from_str(&config.network.ipv4.subnet)?; - let ipv6_network = Ipv6Network::from_str(&config.network.ipv6.subnet)?; - let network_reservation_store = NetworkReservationStore::open(database)?; - let network = NetworkAssignment::new( - host_uuid, - ipv4_network, - ipv6_network, - network_reservation_store, - ) - .await?; - debug!("initializing zone reconciler"); - let zone_reconciler = ZoneReconciler::new( - devices.clone(), - zlt.clone(), - zones.clone(), - events.clone(), - runtime_for_reconciler, - packer.clone(), - zone_reconciler_notify.clone(), - kernel_path, - initrd_path, - addons_path, - network.clone(), - config.clone(), - )?; - - let zone_reconciler_task = zone_reconciler.launch(zone_reconciler_receiver).await?; - let generator_task = generator.launch().await?; - - // TODO: Create a way of abstracting early init tasks in kratad. - // TODO: Make initial power management policy configurable. - let power = runtime.power_management_context().await?; - power.set_smt_policy(true).await?; - power - .set_scheduler_policy("performance".to_string()) - .await?; - info!("power management initialized"); - - info!("krata daemon initialized"); - Ok(Self { - store, - _config: config, - zlt, - devices, - zones, - network, - events, - zone_reconciler_task, - zone_reconciler_notify, - generator_task, - idm, - console, - packer, - runtime, - }) - } - - pub async fn listen(&mut self, addr: ControlDialAddress) -> Result<()> { - debug!("starting control service"); - let control_service = DaemonControlService::new( - self.zlt.clone(), - self.devices.clone(), - self.events.clone(), - self.console.clone(), - self.idm.clone(), - self.zones.clone(), - self.network.clone(), - self.zone_reconciler_notify.clone(), - self.packer.clone(), - self.runtime.clone(), - ); - - let mut server = Server::builder(); - - if let ControlDialAddress::Tls { - host: _, - port: _, - insecure, - } = &addr - { - let mut tls_config = ServerTlsConfig::new(); - if !insecure { - let certificate_path = format!("{}/tls/daemon.pem", self.store); - let key_path = format!("{}/tls/daemon.key", self.store); - tls_config = tls_config.identity(Identity::from_pem(certificate_path, key_path)); - } - server = server.tls_config(tls_config)?; - } - - server = server.http2_keepalive_interval(Some(Duration::from_secs(10))); - - let server = server.add_service(ControlServiceServer::new(control_service)); - info!("listening on address {}", addr); - match addr { - ControlDialAddress::UnixSocket { path } => { - let path = PathBuf::from(path); - if path.exists() { - fs::remove_file(&path).await?; - } - let listener = UnixListener::bind(path)?; - let stream = UnixListenerStream::new(listener); - server.serve_with_incoming(stream).await?; - } - - ControlDialAddress::Tcp { host, port } => { - let address = format!("{}:{}", host, port); - server.serve(SocketAddr::from_str(&address)?).await?; - } - - ControlDialAddress::Tls { - host, - port, - insecure: _, - } => { - let address = format!("{}:{}", host, port); - server.serve(SocketAddr::from_str(&address)?).await?; - } - } - Ok(()) - } -} - -impl Drop for Daemon { - fn drop(&mut self) { - self.zone_reconciler_task.abort(); - self.generator_task.abort(); - } -} - -fn detect_zone_path(store: &str, name: &str) -> Result { - let mut path = PathBuf::from(format!("{}/zone/{}", store, name)); - if path.is_file() { - return Ok(path); - } - - path = PathBuf::from(format!("/usr/share/krata/zone/{}", name)); - if path.is_file() { - return Ok(path); - } - Err(anyhow!("unable to find required zone file: {}", name)) -} diff --git a/crates/daemon/src/metrics.rs b/crates/daemon/src/metrics.rs deleted file mode 100644 index 1390b545..00000000 --- a/crates/daemon/src/metrics.rs +++ /dev/null @@ -1,27 +0,0 @@ -use krata::{ - idm::internal::{MetricFormat, MetricNode}, - v1::common::{ZoneMetricFormat, ZoneMetricNode}, -}; - -fn idm_metric_format_to_api(format: MetricFormat) -> ZoneMetricFormat { - match format { - MetricFormat::Unknown => ZoneMetricFormat::Unknown, - MetricFormat::Bytes => ZoneMetricFormat::Bytes, - MetricFormat::Integer => ZoneMetricFormat::Integer, - MetricFormat::DurationSeconds => ZoneMetricFormat::DurationSeconds, - } -} - -pub fn idm_metric_to_api(node: MetricNode) -> ZoneMetricNode { - let format = node.format(); - ZoneMetricNode { - name: node.name, - value: node.value, - format: idm_metric_format_to_api(format).into(), - children: node - .children - .into_iter() - .map(idm_metric_to_api) - .collect::>(), - } -} diff --git a/crates/daemon/src/network/assignment.rs b/crates/daemon/src/network/assignment.rs deleted file mode 100644 index 7053ff04..00000000 --- a/crates/daemon/src/network/assignment.rs +++ /dev/null @@ -1,204 +0,0 @@ -use advmac::MacAddr6; -use anyhow::{anyhow, Result}; -use ipnetwork::{Ipv4Network, Ipv6Network}; -use std::{ - collections::HashMap, - net::{Ipv4Addr, Ipv6Addr}, - sync::Arc, -}; -use tokio::sync::RwLock; -use uuid::Uuid; - -use crate::db::network::{NetworkReservation, NetworkReservationStore}; - -#[derive(Default, Clone)] -pub struct NetworkAssignmentState { - pub ipv4: HashMap, - pub ipv6: HashMap, -} - -#[derive(Clone)] -pub struct NetworkAssignment { - ipv4_network: Ipv4Network, - ipv6_network: Ipv6Network, - gateway_ipv4: Ipv4Addr, - gateway_ipv6: Ipv6Addr, - gateway_mac: MacAddr6, - store: NetworkReservationStore, - state: Arc>, -} - -impl NetworkAssignment { - pub async fn new( - host_uuid: Uuid, - ipv4_network: Ipv4Network, - ipv6_network: Ipv6Network, - store: NetworkReservationStore, - ) -> Result { - let mut state = NetworkAssignment::fetch_current_state(&store).await?; - let gateway_reservation = if let Some(reservation) = store.read(Uuid::nil()).await? { - reservation - } else { - NetworkAssignment::allocate( - &mut state, - &store, - Uuid::nil(), - ipv4_network, - ipv6_network, - None, - None, - None, - ) - .await? - }; - - if store.read(host_uuid).await?.is_none() { - let _ = NetworkAssignment::allocate( - &mut state, - &store, - host_uuid, - ipv4_network, - ipv6_network, - Some(gateway_reservation.gateway_ipv4), - Some(gateway_reservation.gateway_ipv6), - Some(gateway_reservation.gateway_mac), - ) - .await?; - } - - let assignment = NetworkAssignment { - ipv4_network, - ipv6_network, - gateway_ipv4: gateway_reservation.ipv4, - gateway_ipv6: gateway_reservation.ipv6, - gateway_mac: gateway_reservation.mac, - store, - state: Arc::new(RwLock::new(state)), - }; - Ok(assignment) - } - - async fn fetch_current_state( - store: &NetworkReservationStore, - ) -> Result { - let reservations = store.list().await?; - let mut state = NetworkAssignmentState::default(); - for reservation in reservations.values() { - state.ipv4.insert(reservation.ipv4, reservation.clone()); - state.ipv6.insert(reservation.ipv6, reservation.clone()); - } - Ok(state) - } - - #[allow(clippy::too_many_arguments)] - async fn allocate( - state: &mut NetworkAssignmentState, - store: &NetworkReservationStore, - uuid: Uuid, - ipv4_network: Ipv4Network, - ipv6_network: Ipv6Network, - gateway_ipv4: Option, - gateway_ipv6: Option, - gateway_mac: Option, - ) -> Result { - let found_ipv4: Option = ipv4_network - .iter() - .filter(|ip| { - ip.is_private() && !(ip.is_loopback() || ip.is_multicast() || ip.is_broadcast()) - }) - .filter(|ip| { - let last = ip.octets()[3]; - // filter for IPs ending in .1 to .250 because .250+ can have special meaning - (1..250).contains(&last) - }) - .find(|ip| !state.ipv4.contains_key(ip)); - - let found_ipv6: Option = ipv6_network - .iter() - .filter(|ip| !ip.is_loopback() && !ip.is_multicast()) - .filter(|ip| { - let last = ip.octets()[15]; - last > 0 - }) - .find(|ip| !state.ipv6.contains_key(ip)); - - let Some(ipv4) = found_ipv4 else { - return Err(anyhow!( - "unable to allocate ipv4 address, assigned network is exhausted" - )); - }; - - let Some(ipv6) = found_ipv6 else { - return Err(anyhow!( - "unable to allocate ipv6 address, assigned network is exhausted" - )); - }; - - let mut mac = MacAddr6::random(); - mac.set_local(true); - mac.set_multicast(false); - - let reservation = NetworkReservation { - uuid: uuid.to_string(), - ipv4, - ipv6, - mac, - ipv4_prefix: ipv4_network.prefix(), - ipv6_prefix: ipv6_network.prefix(), - gateway_ipv4: gateway_ipv4.unwrap_or(ipv4), - gateway_ipv6: gateway_ipv6.unwrap_or(ipv6), - gateway_mac: gateway_mac.unwrap_or(mac), - }; - state.ipv4.insert(ipv4, reservation.clone()); - state.ipv6.insert(ipv6, reservation.clone()); - store.update(uuid, reservation.clone()).await?; - Ok(reservation) - } - - pub async fn assign(&self, uuid: Uuid) -> Result { - let mut state = self.state.write().await; - let reservation = NetworkAssignment::allocate( - &mut state, - &self.store, - uuid, - self.ipv4_network, - self.ipv6_network, - Some(self.gateway_ipv4), - Some(self.gateway_ipv6), - Some(self.gateway_mac), - ) - .await?; - Ok(reservation) - } - - pub async fn recall(&self, uuid: Uuid) -> Result<()> { - let mut state = self.state.write().await; - self.store.remove(uuid).await?; - state - .ipv4 - .retain(|_, reservation| reservation.uuid != uuid.to_string()); - state - .ipv6 - .retain(|_, reservation| reservation.uuid != uuid.to_string()); - Ok(()) - } - - pub async fn retrieve(&self, uuid: Uuid) -> Result> { - self.store.read(uuid).await - } - - pub async fn reload(&self) -> Result<()> { - let mut state = self.state.write().await; - let intermediate = NetworkAssignment::fetch_current_state(&self.store).await?; - *state = intermediate; - Ok(()) - } - - pub async fn read(&self) -> Result { - Ok(self.state.read().await.clone()) - } - - pub async fn read_reservations(&self) -> Result> { - self.store.list().await - } -} diff --git a/crates/daemon/src/network/mod.rs b/crates/daemon/src/network/mod.rs deleted file mode 100644 index 53a13b47..00000000 --- a/crates/daemon/src/network/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod assignment; diff --git a/crates/daemon/src/oci.rs b/crates/daemon/src/oci.rs deleted file mode 100644 index 0a601ef4..00000000 --- a/crates/daemon/src/oci.rs +++ /dev/null @@ -1,79 +0,0 @@ -use krata::v1::control::{ - image_progress_indication::Indication, ImageProgress, ImageProgressIndication, - ImageProgressIndicationBar, ImageProgressIndicationCompleted, ImageProgressIndicationHidden, - ImageProgressIndicationSpinner, ImageProgressLayer, ImageProgressLayerPhase, - ImageProgressPhase, -}; -use krataoci::progress::{ - OciProgress, OciProgressIndication, OciProgressLayer, OciProgressLayerPhase, OciProgressPhase, -}; - -fn convert_oci_progress_indication(indication: OciProgressIndication) -> ImageProgressIndication { - ImageProgressIndication { - indication: Some(match indication { - OciProgressIndication::Hidden => Indication::Hidden(ImageProgressIndicationHidden {}), - OciProgressIndication::ProgressBar { - message, - current, - total, - bytes, - } => Indication::Bar(ImageProgressIndicationBar { - message: message.unwrap_or_default(), - current, - total, - is_bytes: bytes, - }), - OciProgressIndication::Spinner { message } => { - Indication::Spinner(ImageProgressIndicationSpinner { - message: message.unwrap_or_default(), - }) - } - OciProgressIndication::Completed { - message, - total, - bytes, - } => Indication::Completed(ImageProgressIndicationCompleted { - message: message.unwrap_or_default(), - total: total.unwrap_or(0), - is_bytes: bytes, - }), - }), - } -} - -fn convert_oci_layer_progress(layer: OciProgressLayer) -> ImageProgressLayer { - ImageProgressLayer { - id: layer.id, - phase: match layer.phase { - OciProgressLayerPhase::Waiting => ImageProgressLayerPhase::Waiting, - OciProgressLayerPhase::Downloading => ImageProgressLayerPhase::Downloading, - OciProgressLayerPhase::Downloaded => ImageProgressLayerPhase::Downloaded, - OciProgressLayerPhase::Extracting => ImageProgressLayerPhase::Extracting, - OciProgressLayerPhase::Extracted => ImageProgressLayerPhase::Extracted, - } - .into(), - indication: Some(convert_oci_progress_indication(layer.indication)), - } -} - -pub fn convert_oci_progress(oci: OciProgress) -> ImageProgress { - ImageProgress { - phase: match oci.phase { - OciProgressPhase::Started => ImageProgressPhase::Started, - OciProgressPhase::Resolving => ImageProgressPhase::Resolving, - OciProgressPhase::Resolved => ImageProgressPhase::Resolved, - OciProgressPhase::ConfigDownload => ImageProgressPhase::ConfigDownload, - OciProgressPhase::LayerDownload => ImageProgressPhase::LayerDownload, - OciProgressPhase::Assemble => ImageProgressPhase::Assemble, - OciProgressPhase::Pack => ImageProgressPhase::Pack, - OciProgressPhase::Complete => ImageProgressPhase::Complete, - } - .into(), - layers: oci - .layers - .into_values() - .map(convert_oci_layer_progress) - .collect::>(), - indication: Some(convert_oci_progress_indication(oci.indication)), - } -} diff --git a/crates/daemon/src/reconcile/mod.rs b/crates/daemon/src/reconcile/mod.rs deleted file mode 100644 index 3c29b2c1..00000000 --- a/crates/daemon/src/reconcile/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod zone; diff --git a/crates/daemon/src/reconcile/zone/create.rs b/crates/daemon/src/reconcile/zone/create.rs deleted file mode 100644 index 90f2b56b..00000000 --- a/crates/daemon/src/reconcile/zone/create.rs +++ /dev/null @@ -1,259 +0,0 @@ -use anyhow::{anyhow, Result}; -use futures::StreamExt; -use krata::launchcfg::LaunchPackedFormat; -use krata::v1::common::{OciImageFormat, Zone, ZoneState, ZoneStatus}; -use krata::v1::common::{ZoneOciImageSpec, ZoneResourceStatus}; -use krataoci::packer::{service::OciPackerService, OciPackedFormat}; -use kratart::launch::{PciBdf, PciDevice, PciRdmReservePolicy, ZoneLaunchNetwork}; -use kratart::{launch::ZoneLaunchRequest, Runtime}; -use log::info; -use std::collections::HashMap; -use std::path::{Path, PathBuf}; -use std::str::FromStr; -use std::sync::atomic::{AtomicBool, Ordering}; - -use crate::config::{DaemonConfig, DaemonPciDeviceRdmReservePolicy}; -use crate::devices::DaemonDeviceManager; -use crate::network::assignment::NetworkAssignment; -use crate::reconcile::zone::network_reservation_to_network_status; -use crate::{reconcile::zone::ZoneReconcilerResult, zlt::ZoneLookupTable}; -use krata::v1::common::zone_image_spec::Image; -use tokio::fs::{self, File}; -use tokio::io::AsyncReadExt; -use tokio_tar::Archive; -use uuid::Uuid; - -pub struct ZoneCreator<'a> { - pub devices: &'a DaemonDeviceManager, - pub kernel_path: &'a Path, - pub initrd_path: &'a Path, - pub addons_path: &'a Path, - pub packer: &'a OciPackerService, - pub network_assignment: &'a NetworkAssignment, - pub zlt: &'a ZoneLookupTable, - pub runtime: &'a Runtime, - pub config: &'a DaemonConfig, -} - -impl ZoneCreator<'_> { - pub async fn oci_spec_tar_read_file( - &self, - file: &Path, - oci: &ZoneOciImageSpec, - ) -> Result> { - if oci.format() != OciImageFormat::Tar { - return Err(anyhow!( - "oci image spec for {} is required to be in tar format", - oci.digest - )); - } - - let image = self - .packer - .recall(&oci.digest, OciPackedFormat::Tar) - .await?; - - let Some(image) = image else { - return Err(anyhow!("image {} was not found in tar format", oci.digest)); - }; - - let mut archive = Archive::new(File::open(&image.path).await?); - let mut entries = archive.entries()?; - while let Some(entry) = entries.next().await { - let mut entry = entry?; - let path = entry.path()?; - if path == file { - let mut buffer = Vec::new(); - entry.read_to_end(&mut buffer).await?; - return Ok(buffer); - } - } - Err(anyhow!( - "unable to find file {} in image {}", - file.to_string_lossy(), - oci.digest - )) - } - - pub async fn create(&self, uuid: Uuid, zone: &mut Zone) -> Result { - let Some(ref mut spec) = zone.spec else { - return Err(anyhow!("zone spec not specified")); - }; - - let Some(ref image) = spec.image else { - return Err(anyhow!("image spec not provided")); - }; - let oci = match image.image { - Some(Image::Oci(ref oci)) => oci, - None => { - return Err(anyhow!("oci spec not specified")); - } - }; - let task = spec.task.as_ref().cloned().unwrap_or_default(); - - let image = self - .packer - .recall( - &oci.digest, - match oci.format() { - OciImageFormat::Unknown => OciPackedFormat::Squashfs, - OciImageFormat::Squashfs => OciPackedFormat::Squashfs, - OciImageFormat::Erofs => OciPackedFormat::Erofs, - OciImageFormat::Tar => { - return Err(anyhow!("tar image format is not supported for zones")); - } - }, - ) - .await?; - - let Some(image) = image else { - return Err(anyhow!( - "image {} in the requested format did not exist", - oci.digest - )); - }; - - let kernel = if let Some(ref spec) = spec.kernel { - let Some(Image::Oci(ref oci)) = spec.image else { - return Err(anyhow!("kernel image spec must be an oci image")); - }; - self.oci_spec_tar_read_file(&PathBuf::from("kernel/image"), oci) - .await? - } else { - fs::read(&self.kernel_path).await? - }; - let initrd = if let Some(ref spec) = spec.initrd { - let Some(Image::Oci(ref oci)) = spec.image else { - return Err(anyhow!("initrd image spec must be an oci image")); - }; - self.oci_spec_tar_read_file(&PathBuf::from("krata/initrd"), oci) - .await? - } else { - fs::read(&self.initrd_path).await? - }; - - let success = AtomicBool::new(false); - - let _device_release_guard = scopeguard::guard( - (spec.devices.clone(), self.devices.clone()), - |(devices, manager)| { - if !success.load(Ordering::Acquire) { - tokio::task::spawn(async move { - for device in devices { - let _ = manager.release(&device.name, uuid).await; - } - }); - } - }, - ); - - let mut pcis = Vec::new(); - for device in &spec.devices { - let state = self.devices.claim(&device.name, uuid).await?; - if let Some(cfg) = state.pci { - for location in cfg.locations { - let pci = PciDevice { - bdf: PciBdf::from_str(&location)?.with_domain(0), - permissive: cfg.permissive, - msi_translate: cfg.msi_translate, - power_management: cfg.power_management, - rdm_reserve_policy: match cfg.rdm_reserve_policy { - DaemonPciDeviceRdmReservePolicy::Strict => PciRdmReservePolicy::Strict, - DaemonPciDeviceRdmReservePolicy::Relaxed => { - PciRdmReservePolicy::Relaxed - } - }, - }; - pcis.push(pci); - } - } else { - return Err(anyhow!( - "device '{}' isn't a known device type", - device.name - )); - } - } - - let reservation = self.network_assignment.assign(uuid).await?; - - let mut initial_resources = spec.initial_resources.unwrap_or_default(); - if initial_resources.target_cpus < 1 { - initial_resources.target_cpus = 1; - } - if initial_resources.target_cpus > initial_resources.max_cpus { - initial_resources.max_cpus = initial_resources.target_cpus; - } - spec.initial_resources = Some(initial_resources); - let kernel_options = spec.kernel_options.clone().unwrap_or_default(); - let info = self - .runtime - .launch(ZoneLaunchRequest { - format: match image.format { - OciPackedFormat::Squashfs => LaunchPackedFormat::Squashfs, - OciPackedFormat::Erofs => LaunchPackedFormat::Erofs, - _ => { - return Err(anyhow!( - "oci image is in an invalid format, which isn't compatible with launch" - )); - } - }, - uuid: Some(uuid), - name: if spec.name.is_empty() { - None - } else { - Some(spec.name.clone()) - }, - image, - kernel, - initrd, - target_cpus: initial_resources.target_cpus, - max_cpus: initial_resources.max_cpus, - max_memory: initial_resources.max_memory, - target_memory: initial_resources.target_memory, - pcis, - env: task - .environment - .iter() - .map(|x| (x.key.clone(), x.value.clone())) - .collect::>(), - run: empty_vec_optional(task.command.clone()), - kernel_verbose: kernel_options.verbose, - kernel_cmdline_append: kernel_options.cmdline_append, - addons_image: Some(self.addons_path.to_path_buf()), - network: ZoneLaunchNetwork { - ipv4: reservation.ipv4.to_string(), - ipv4_prefix: reservation.ipv4_prefix, - ipv6: reservation.ipv6.to_string(), - ipv6_prefix: reservation.ipv6_prefix, - gateway_ipv4: reservation.gateway_ipv4.to_string(), - gateway_ipv6: reservation.gateway_ipv6.to_string(), - zone_mac: reservation.mac, - nameservers: self.config.network.nameservers.clone(), - }, - }) - .await?; - self.zlt.associate(uuid, info.domid).await; - info!("created zone {}", uuid); - zone.status = Some(ZoneStatus { - state: ZoneState::Created.into(), - network_status: Some(network_reservation_to_network_status(&reservation)), - exit_status: None, - error_status: None, - resource_status: Some(ZoneResourceStatus { - active_resources: Some(initial_resources), - }), - host: self.zlt.host_uuid().to_string(), - domid: info.domid, - }); - success.store(true, Ordering::Release); - Ok(ZoneReconcilerResult::Changed { rerun: false }) - } -} - -fn empty_vec_optional(value: Vec) -> Option> { - if value.is_empty() { - None - } else { - Some(value) - } -} diff --git a/crates/daemon/src/reconcile/zone/mod.rs b/crates/daemon/src/reconcile/zone/mod.rs deleted file mode 100644 index 7c702c6f..00000000 --- a/crates/daemon/src/reconcile/zone/mod.rs +++ /dev/null @@ -1,381 +0,0 @@ -use std::{ - collections::{hash_map::Entry, HashMap}, - path::PathBuf, - sync::Arc, - time::Duration, -}; - -use self::create::ZoneCreator; -use crate::config::DaemonConfig; -use crate::db::network::NetworkReservation; -use crate::network::assignment::NetworkAssignment; -use crate::{ - db::zone::ZoneStore, - devices::DaemonDeviceManager, - event::{DaemonEvent, DaemonEventContext}, - zlt::ZoneLookupTable, -}; -use anyhow::Result; -use krata::v1::{ - common::{Zone, ZoneErrorStatus, ZoneExitStatus, ZoneNetworkStatus, ZoneState, ZoneStatus}, - control::ZoneChangedEvent, -}; -use krataoci::packer::service::OciPackerService; -use kratart::Runtime; -use log::{error, info, trace, warn}; -use tokio::{ - select, - sync::{ - mpsc::{channel, Receiver, Sender}, - RwLock, - }, - task::JoinHandle, - time::sleep, -}; -use uuid::Uuid; - -mod create; - -const PARALLEL_LIMIT: u32 = 5; - -#[derive(Debug)] -enum ZoneReconcilerResult { - Unchanged, - Changed { rerun: bool }, -} - -struct ZoneReconcilerEntry { - sender: Sender<()>, -} - -#[derive(Clone)] -pub struct ZoneReconciler { - devices: DaemonDeviceManager, - zlt: ZoneLookupTable, - zones: ZoneStore, - events: DaemonEventContext, - runtime: Runtime, - packer: OciPackerService, - kernel_path: PathBuf, - initrd_path: PathBuf, - addons_path: PathBuf, - tasks: Arc>>, - zone_reconciler_notify: Sender, - zone_reconcile_lock: Arc>, - ip_assignment: NetworkAssignment, - config: Arc, -} - -impl ZoneReconciler { - #[allow(clippy::too_many_arguments)] - pub fn new( - devices: DaemonDeviceManager, - zlt: ZoneLookupTable, - zones: ZoneStore, - events: DaemonEventContext, - runtime: Runtime, - packer: OciPackerService, - zone_reconciler_notify: Sender, - kernel_path: PathBuf, - initrd_path: PathBuf, - modules_path: PathBuf, - ip_assignment: NetworkAssignment, - config: Arc, - ) -> Result { - Ok(Self { - devices, - zlt, - zones, - events, - runtime, - packer, - kernel_path, - initrd_path, - addons_path: modules_path, - tasks: Arc::new(RwLock::new(HashMap::new())), - zone_reconciler_notify, - zone_reconcile_lock: Arc::new(RwLock::with_max_readers((), PARALLEL_LIMIT)), - ip_assignment, - config, - }) - } - - pub async fn launch(self, mut notify: Receiver) -> Result> { - Ok(tokio::task::spawn(async move { - if let Err(error) = self.reconcile_runtime(true).await { - error!("runtime reconciler failed: {}", error); - } - - loop { - select! { - x = notify.recv() => match x { - None => { - break; - }, - - Some(uuid) => { - if let Err(error) = self.launch_task_if_needed(uuid).await { - error!("failed to start zone reconciler task {}: {}", uuid, error); - } - - let map = self.tasks.read().await; - if let Some(entry) = map.get(&uuid) { - if let Err(error) = entry.sender.send(()).await { - error!("failed to notify zone reconciler task {}: {}", uuid, error); - } - } - } - }, - - _ = sleep(Duration::from_secs(15)) => { - if let Err(error) = self.reconcile_runtime(false).await { - error!("runtime reconciler failed: {}", error); - } - } - } - } - })) - } - - pub async fn reconcile_runtime(&self, initial: bool) -> Result<()> { - let _permit = self.zone_reconcile_lock.write().await; - trace!("reconciling runtime"); - let runtime_zones = self.runtime.list().await?; - let stored_zones = self.zones.list().await?; - - let non_existent_zones = runtime_zones - .iter() - .filter(|x| !stored_zones.iter().any(|g| *g.0 == x.uuid)) - .collect::>(); - - for zone in non_existent_zones { - warn!("destroying unknown runtime zone {}", zone.uuid); - if let Err(error) = self.runtime.destroy(zone.uuid).await { - error!( - "failed to destroy unknown runtime zone {}: {}", - zone.uuid, error - ); - } - self.zones.remove(zone.uuid).await?; - } - - let mut device_claims = HashMap::new(); - - for (uuid, mut stored_zone) in stored_zones { - let previous_zone = stored_zone.clone(); - let runtime_zone = runtime_zones.iter().find(|x| x.uuid == uuid); - match runtime_zone { - None => { - let mut status = stored_zone.status.as_mut().cloned().unwrap_or_default(); - if status.state() == ZoneState::Created { - status.state = ZoneState::Creating.into(); - } - stored_zone.status = Some(status); - } - - Some(runtime) => { - self.zlt.associate(uuid, runtime.domid).await; - let mut status = stored_zone.status.as_mut().cloned().unwrap_or_default(); - if let Some(code) = runtime.state.exit_code { - status.state = ZoneState::Exited.into(); - status.exit_status = Some(ZoneExitStatus { code }); - } else { - status.state = ZoneState::Created.into(); - } - - for device in &stored_zone - .spec - .as_ref() - .cloned() - .unwrap_or_default() - .devices - { - device_claims.insert(device.name.clone(), uuid); - } - - if let Some(reservation) = self.ip_assignment.retrieve(uuid).await? { - status.network_status = - Some(network_reservation_to_network_status(&reservation)); - } - stored_zone.status = Some(status); - } - } - - let changed = stored_zone != previous_zone; - - if changed || initial { - self.zones.update(uuid, stored_zone).await?; - let _ = self.zone_reconciler_notify.try_send(uuid); - } - } - - self.devices.update_claims(device_claims).await?; - - Ok(()) - } - - pub async fn reconcile(&self, uuid: Uuid) -> Result { - let _runtime_reconcile_permit = self.zone_reconcile_lock.read().await; - let Some(mut zone) = self.zones.read(uuid).await? else { - warn!( - "notified of reconcile for zone {} but it didn't exist", - uuid - ); - return Ok(false); - }; - - info!("reconciling zone {}", uuid); - - self.events - .send(DaemonEvent::ZoneChanged(ZoneChangedEvent { - zone: Some(zone.clone()), - }))?; - - let start_state = zone.status.as_ref().map(|x| x.state()).unwrap_or_default(); - let result = match start_state { - ZoneState::Creating => self.create(uuid, &mut zone).await, - ZoneState::Exited => self.exited(&mut zone).await, - ZoneState::Destroying => self.destroy(uuid, &mut zone).await, - _ => Ok(ZoneReconcilerResult::Unchanged), - }; - - let result = match result { - Ok(result) => result, - Err(error) => { - zone.status = Some(zone.status.as_mut().cloned().unwrap_or_default()); - zone.status.as_mut().unwrap().state = ZoneState::Failed.into(); - zone.status.as_mut().unwrap().error_status = Some(ZoneErrorStatus { - message: error.to_string(), - }); - warn!("failed to start zone {}: {}", zone.id, error); - ZoneReconcilerResult::Changed { rerun: false } - } - }; - - info!("reconciled zone {}", uuid); - - let state = zone.status.as_ref().map(|x| x.state()).unwrap_or_default(); - let destroyed = state == ZoneState::Destroyed; - - let rerun = if let ZoneReconcilerResult::Changed { rerun } = result { - let event = DaemonEvent::ZoneChanged(ZoneChangedEvent { - zone: Some(zone.clone()), - }); - - if destroyed { - self.zones.remove(uuid).await?; - let mut map = self.tasks.write().await; - map.remove(&uuid); - } else { - self.zones.update(uuid, zone.clone()).await?; - } - - self.events.send(event)?; - rerun - } else { - false - }; - - Ok(rerun) - } - - async fn create(&self, uuid: Uuid, zone: &mut Zone) -> Result { - let starter = ZoneCreator { - devices: &self.devices, - kernel_path: &self.kernel_path, - initrd_path: &self.initrd_path, - addons_path: &self.addons_path, - packer: &self.packer, - network_assignment: &self.ip_assignment, - zlt: &self.zlt, - runtime: &self.runtime, - config: &self.config, - }; - starter.create(uuid, zone).await - } - - async fn exited(&self, zone: &mut Zone) -> Result { - if let Some(ref mut status) = zone.status { - status.set_state(ZoneState::Destroying); - Ok(ZoneReconcilerResult::Changed { rerun: true }) - } else { - Ok(ZoneReconcilerResult::Unchanged) - } - } - - async fn destroy(&self, uuid: Uuid, zone: &mut Zone) -> Result { - if let Err(error) = self.runtime.destroy(uuid).await { - trace!("failed to destroy runtime zone {}: {}", uuid, error); - } - - let domid = zone.status.as_ref().map(|x| x.domid); - - if let Some(domid) = domid { - self.zlt.remove(uuid, domid).await; - } - - info!("destroyed zone {}", uuid); - self.ip_assignment.recall(uuid).await?; - zone.status = Some(ZoneStatus { - state: ZoneState::Destroyed.into(), - network_status: None, - exit_status: None, - error_status: None, - resource_status: None, - host: self.zlt.host_uuid().to_string(), - domid: domid.unwrap_or(u32::MAX), - }); - self.devices.release_all(uuid).await?; - Ok(ZoneReconcilerResult::Changed { rerun: false }) - } - - async fn launch_task_if_needed(&self, uuid: Uuid) -> Result<()> { - let mut map = self.tasks.write().await; - match map.entry(uuid) { - Entry::Occupied(_) => {} - Entry::Vacant(entry) => { - entry.insert(self.launch_task(uuid).await?); - } - } - Ok(()) - } - - async fn launch_task(&self, uuid: Uuid) -> Result { - let this = self.clone(); - let (sender, mut receiver) = channel(10); - tokio::task::spawn(async move { - 'notify_loop: loop { - if receiver.recv().await.is_none() { - break 'notify_loop; - } - - 'rerun_loop: loop { - let rerun = match this.reconcile(uuid).await { - Ok(rerun) => rerun, - Err(error) => { - error!("failed to reconcile zone {}: {}", uuid, error); - false - } - }; - - if rerun { - continue 'rerun_loop; - } - break 'rerun_loop; - } - } - }); - Ok(ZoneReconcilerEntry { sender }) - } -} - -pub fn network_reservation_to_network_status(ip: &NetworkReservation) -> ZoneNetworkStatus { - ZoneNetworkStatus { - zone_ipv4: format!("{}/{}", ip.ipv4, ip.ipv4_prefix), - zone_ipv6: format!("{}/{}", ip.ipv6, ip.ipv6_prefix), - zone_mac: ip.mac.to_string().to_lowercase().replace('-', ":"), - gateway_ipv4: format!("{}/{}", ip.gateway_ipv4, ip.ipv4_prefix), - gateway_ipv6: format!("{}/{}", ip.gateway_ipv6, ip.ipv6_prefix), - gateway_mac: ip.gateway_mac.to_string().to_lowercase().replace('-', ":"), - } -} diff --git a/crates/daemon/src/zlt.rs b/crates/daemon/src/zlt.rs deleted file mode 100644 index 48320037..00000000 --- a/crates/daemon/src/zlt.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::{collections::HashMap, sync::Arc}; - -use tokio::sync::RwLock; -use uuid::Uuid; - -struct ZoneLookupTableState { - domid_to_uuid: HashMap, - uuid_to_domid: HashMap, -} - -impl ZoneLookupTableState { - pub fn new(host_uuid: Uuid) -> Self { - let mut domid_to_uuid = HashMap::new(); - let mut uuid_to_domid = HashMap::new(); - domid_to_uuid.insert(0, host_uuid); - uuid_to_domid.insert(host_uuid, 0); - ZoneLookupTableState { - domid_to_uuid, - uuid_to_domid, - } - } -} - -#[derive(Clone)] -pub struct ZoneLookupTable { - host_domid: u32, - host_uuid: Uuid, - state: Arc>, -} - -impl ZoneLookupTable { - pub fn new(host_domid: u32, host_uuid: Uuid) -> Self { - ZoneLookupTable { - host_domid, - host_uuid, - state: Arc::new(RwLock::new(ZoneLookupTableState::new(host_uuid))), - } - } - - pub fn host_uuid(&self) -> Uuid { - self.host_uuid - } - - pub fn host_domid(&self) -> u32 { - self.host_domid - } - - pub async fn lookup_uuid_by_domid(&self, domid: u32) -> Option { - let state = self.state.read().await; - state.domid_to_uuid.get(&domid).cloned() - } - - pub async fn lookup_domid_by_uuid(&self, uuid: &Uuid) -> Option { - let state = self.state.read().await; - state.uuid_to_domid.get(uuid).cloned() - } - - pub async fn associate(&self, uuid: Uuid, domid: u32) { - let mut state = self.state.write().await; - state.uuid_to_domid.insert(uuid, domid); - state.domid_to_uuid.insert(domid, uuid); - } - - pub async fn remove(&self, uuid: Uuid, domid: u32) { - let mut state = self.state.write().await; - state.uuid_to_domid.remove(&uuid); - state.domid_to_uuid.remove(&domid); - } -} diff --git a/crates/krata/Cargo.toml b/crates/krata/Cargo.toml deleted file mode 100644 index ca177f03..00000000 --- a/crates/krata/Cargo.toml +++ /dev/null @@ -1,45 +0,0 @@ -[package] -name = "krata" -description = "Client library and common services for the krata isolation engine" -license.workspace = true -version.workspace = true -homepage.workspace = true -repository.workspace = true -edition = "2021" -resolver = "2" - -[dependencies] -anyhow = { workspace = true } -async-trait = { workspace = true } -bytes = { workspace = true } -libc = { workspace = true } -log = { workspace = true } -once_cell = { workspace = true } -pin-project-lite = { workspace = true } -prost = { workspace = true } -prost-reflect = { workspace = true } -prost-types = { workspace = true } -scopeguard = { workspace = true } -serde = { workspace = true } -tonic = { workspace = true } -tokio = { workspace = true } -tokio-stream = { workspace = true } -tower = { workspace = true } -url = { workspace = true } - -[target.'cfg(unix)'.dependencies] -hyper = { workspace = true } -hyper-util = { workspace = true } -nix = { workspace = true, features = ["term"] } - -[build-dependencies] -tonic-build = { workspace = true } -prost-build = { workspace = true } -prost-reflect-build = { workspace = true } - -[lib] -name = "krata" - -[[example]] -name = "ethtool" -path = "examples/ethtool.rs" diff --git a/crates/krata/build.rs b/crates/krata/build.rs deleted file mode 100644 index adffbbc5..00000000 --- a/crates/krata/build.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::io::Result; - -fn main() -> Result<()> { - let mut config = prost_build::Config::new(); - prost_reflect_build::Builder::new() - .descriptor_pool("crate::DESCRIPTOR_POOL") - .configure( - &mut config, - &[ - "proto/krata/v1/control.proto", - "proto/krata/idm/transport.proto", - "proto/krata/idm/internal.proto", - ], - &["proto/"], - )?; - tonic_build::configure().compile_with_config( - config, - &[ - "proto/krata/v1/control.proto", - "proto/krata/idm/transport.proto", - "proto/krata/idm/internal.proto", - ], - &["proto/"], - )?; - Ok(()) -} diff --git a/crates/krata/examples/ethtool.rs b/crates/krata/examples/ethtool.rs deleted file mode 100644 index a577df74..00000000 --- a/crates/krata/examples/ethtool.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::env; - -use anyhow::Result; -use krata::ethtool::EthtoolHandle; - -fn main() -> Result<()> { - let args = env::args().collect::>(); - let interface = args.get(1).unwrap(); - let mut handle = EthtoolHandle::new()?; - handle.set_gso(interface, false)?; - handle.set_tso(interface, false)?; - Ok(()) -} diff --git a/crates/krata/proto/krata/idm/internal.proto b/crates/krata/proto/krata/idm/internal.proto deleted file mode 100644 index 48349ae0..00000000 --- a/crates/krata/proto/krata/idm/internal.proto +++ /dev/null @@ -1,98 +0,0 @@ -syntax = "proto3"; - -package krata.idm.internal; - -option java_multiple_files = true; -option java_package = "dev.krata.proto.idm.internal"; -option java_outer_classname = "IdmInternalProto"; - -import "google/protobuf/struct.proto"; - -message ExitEvent { - int32 code = 1; -} - -message PingRequest {} - -message PingResponse {} - -message MetricsRequest {} - -message MetricsResponse { - MetricNode root = 1; -} - -message MetricNode { - string name = 1; - google.protobuf.Value value = 2; - MetricFormat format = 3; - repeated MetricNode children = 4; -} - -enum MetricFormat { - METRIC_FORMAT_UNKNOWN = 0; - METRIC_FORMAT_BYTES = 1; - METRIC_FORMAT_INTEGER = 2; - METRIC_FORMAT_DURATION_SECONDS = 3; -} - -message ExecEnvVar { - string key = 1; - string value = 2; -} - -message ExecStreamRequestStart { - repeated ExecEnvVar environment = 1; - repeated string command = 2; - string working_directory = 3; - bool tty = 4; - ExecStreamRequestTerminalSize terminal_size = 5; -} - -message ExecStreamRequestStdin { - bytes data = 1; - bool closed = 2; -} - -message ExecStreamRequestTerminalSize { - uint32 rows = 1; - uint32 columns = 2; -} - -message ExecStreamRequestUpdate { - oneof update { - ExecStreamRequestStart start = 1; - ExecStreamRequestStdin stdin = 2; - ExecStreamRequestTerminalSize terminal_resize = 3; - } -} - -message ExecStreamResponseUpdate { - bool exited = 1; - string error = 2; - int32 exit_code = 3; - bytes stdout = 4; - bytes stderr = 5; -} - -message Event { - oneof event { - ExitEvent exit = 1; - } -} - -message Request { - oneof request { - PingRequest ping = 1; - MetricsRequest metrics = 2; - ExecStreamRequestUpdate exec_stream = 3; - } -} - -message Response { - oneof response { - PingResponse ping = 1; - MetricsResponse metrics = 2; - ExecStreamResponseUpdate exec_stream = 3; - } -} diff --git a/crates/krata/proto/krata/idm/transport.proto b/crates/krata/proto/krata/idm/transport.proto deleted file mode 100644 index 5fc7b86f..00000000 --- a/crates/krata/proto/krata/idm/transport.proto +++ /dev/null @@ -1,27 +0,0 @@ -syntax = "proto3"; - -package krata.idm.transport; - -option java_multiple_files = true; -option java_package = "dev.krata.proto.idm.transport"; -option java_outer_classname = "IdmTransportProto"; - -message IdmTransportPacket { - uint64 id = 1; - uint64 channel = 2; - IdmTransportPacketForm form = 3; - bytes data = 4; -} - -enum IdmTransportPacketForm { - IDM_TRANSPORT_PACKET_FORM_UNKNOWN = 0; - IDM_TRANSPORT_PACKET_FORM_RAW = 1; - IDM_TRANSPORT_PACKET_FORM_EVENT = 2; - IDM_TRANSPORT_PACKET_FORM_REQUEST = 3; - IDM_TRANSPORT_PACKET_FORM_RESPONSE = 4; - IDM_TRANSPORT_PACKET_FORM_STREAM_REQUEST = 5; - IDM_TRANSPORT_PACKET_FORM_STREAM_REQUEST_UPDATE = 6; - IDM_TRANSPORT_PACKET_FORM_STREAM_RESPONSE_UPDATE = 7; - IDM_TRANSPORT_PACKET_FORM_STREAM_REQUEST_CLOSED = 8; - IDM_TRANSPORT_PACKET_FORM_STREAM_RESPONSE_CLOSED = 9; -} diff --git a/crates/krata/proto/krata/v1/common.proto b/crates/krata/proto/krata/v1/common.proto deleted file mode 100644 index 1b098ab6..00000000 --- a/crates/krata/proto/krata/v1/common.proto +++ /dev/null @@ -1,151 +0,0 @@ -syntax = "proto3"; - -package krata.v1.common; - -option java_multiple_files = true; -option java_package = "dev.krata.proto.v1.common"; -option java_outer_classname = "CommonProto"; - -import "google/protobuf/struct.proto"; - -message Zone { - string id = 1; - ZoneSpec spec = 2; - ZoneStatus status = 3; -} - -message ZoneSpec { - string name = 1; - ZoneImageSpec image = 2; - // If not specified, defaults to the daemon default kernel. - ZoneImageSpec kernel = 3; - // If not specified, defaults to the daemon default initrd. - ZoneImageSpec initrd = 4; - ZoneResourceSpec initial_resources = 5; - ZoneTaskSpec task = 6; - repeated ZoneSpecAnnotation annotations = 7; - repeated ZoneSpecDevice devices = 8; - ZoneKernelOptionsSpec kernel_options = 9; -} - -message ZoneResourceSpec { - uint64 max_memory = 1; - uint64 target_memory = 2; - uint32 max_cpus = 3; - uint32 target_cpus = 4; -} - -message ZoneImageSpec { - oneof image { - ZoneOciImageSpec oci = 1; - } -} - -message ZoneKernelOptionsSpec { - bool verbose = 1; - string cmdline_append = 2; -} - -enum OciImageFormat { - OCI_IMAGE_FORMAT_UNKNOWN = 0; - OCI_IMAGE_FORMAT_SQUASHFS = 1; - OCI_IMAGE_FORMAT_EROFS = 2; - // Tar format is not launchable, and is intended for kernel images. - OCI_IMAGE_FORMAT_TAR = 3; -} - -message ZoneOciImageSpec { - string digest = 1; - OciImageFormat format = 2; -} - -message ZoneTaskSpec { - repeated ZoneTaskSpecEnvVar environment = 1; - repeated string command = 2; - string working_directory = 3; - bool tty = 4; -} - -message ZoneTaskSpecEnvVar { - string key = 1; - string value = 2; -} - -message ZoneSpecAnnotation { - string key = 1; - string value = 2; -} - -message ZoneSpecDevice { - string name = 1; -} - -message ZoneStatus { - ZoneState state = 1; - ZoneNetworkStatus network_status = 2; - ZoneExitStatus exit_status = 3; - ZoneErrorStatus error_status = 4; - string host = 5; - uint32 domid = 6; - ZoneResourceStatus resource_status = 7; -} - -enum ZoneState { - ZONE_STATE_UNKNOWN = 0; - ZONE_STATE_CREATING = 1; - ZONE_STATE_CREATED = 2; - ZONE_STATE_EXITED = 3; - ZONE_STATE_DESTROYING = 4; - ZONE_STATE_DESTROYED = 5; - ZONE_STATE_FAILED = 6; -} - -message ZoneNetworkStatus { - string zone_ipv4 = 1; - string zone_ipv6 = 2; - string zone_mac = 3; - string gateway_ipv4 = 4; - string gateway_ipv6 = 5; - string gateway_mac = 6; -} - -message ZoneExitStatus { - int32 code = 1; -} - -message ZoneErrorStatus { - string message = 1; -} - -message ZoneResourceStatus { - ZoneResourceSpec active_resources = 1; -} - -message ZoneMetricNode { - string name = 1; - google.protobuf.Value value = 2; - ZoneMetricFormat format = 3; - repeated ZoneMetricNode children = 4; -} - -enum ZoneMetricFormat { - ZONE_METRIC_FORMAT_UNKNOWN = 0; - ZONE_METRIC_FORMAT_BYTES = 1; - ZONE_METRIC_FORMAT_INTEGER = 2; - ZONE_METRIC_FORMAT_DURATION_SECONDS = 3; -} - -message TerminalSize { - uint32 rows = 1; - uint32 columns = 2; -} - -message NetworkReservation { - string uuid = 1; - string ipv4 = 2; - string ipv6 = 3; - string mac = 4; - string gateway_ipv4 = 5; - string gateway_ipv6 = 6; - string gateway_mac = 7; -} diff --git a/crates/krata/proto/krata/v1/control.proto b/crates/krata/proto/krata/v1/control.proto deleted file mode 100644 index f9bd7744..00000000 --- a/crates/krata/proto/krata/v1/control.proto +++ /dev/null @@ -1,275 +0,0 @@ -syntax = "proto3"; - -package krata.v1.control; - -option java_multiple_files = true; -option java_package = "dev.krata.proto.v1.control"; -option java_outer_classname = "ControlProto"; - -import "krata/idm/transport.proto"; -import "krata/v1/common.proto"; - -service ControlService { - rpc GetHostStatus(GetHostStatusRequest) returns (GetHostStatusReply); - rpc SnoopIdm(SnoopIdmRequest) returns (stream SnoopIdmReply); - rpc GetHostCpuTopology(GetHostCpuTopologyRequest) returns (GetHostCpuTopologyReply); - rpc SetHostPowerManagementPolicy(SetHostPowerManagementPolicyRequest) returns (SetHostPowerManagementPolicyReply); - - rpc ListDevices(ListDevicesRequest) returns (ListDevicesReply); - - rpc ListNetworkReservations(ListNetworkReservationsRequest) returns (ListNetworkReservationsReply); - - rpc PullImage(PullImageRequest) returns (stream PullImageReply); - - rpc CreateZone(CreateZoneRequest) returns (CreateZoneReply); - rpc DestroyZone(DestroyZoneRequest) returns (DestroyZoneReply); - - rpc ResolveZoneId(ResolveZoneIdRequest) returns (ResolveZoneIdReply); - - rpc GetZone(GetZoneRequest) returns (GetZoneReply); - - rpc UpdateZoneResources(UpdateZoneResourcesRequest) returns (UpdateZoneResourcesReply); - - rpc ListZones(ListZonesRequest) returns (ListZonesReply); - - rpc AttachZoneConsole(stream ZoneConsoleRequest) returns (stream ZoneConsoleReply); - rpc ExecInsideZone(stream ExecInsideZoneRequest) returns (stream ExecInsideZoneReply); - rpc ReadZoneMetrics(ReadZoneMetricsRequest) returns (ReadZoneMetricsReply); - - rpc WatchEvents(WatchEventsRequest) returns (stream WatchEventsReply); - - rpc ReadHypervisorConsole(ReadHypervisorConsoleRequest) returns (ReadHypervisorConsoleReply); -} - -message GetHostStatusRequest {} - -message GetHostStatusReply { - string host_uuid = 1; - uint32 host_domid = 2; - string krata_version = 3; - string host_ipv4 = 4; - string host_ipv6 = 5; - string host_mac = 6; -} - -message CreateZoneRequest { - krata.v1.common.ZoneSpec spec = 1; -} - -message CreateZoneReply { - string zone_id = 1; -} - -message DestroyZoneRequest { - string zone_id = 1; -} - -message DestroyZoneReply {} - -message ResolveZoneIdRequest { - string name = 1; -} - -message ResolveZoneIdReply { - string zone_id = 1; -} - -message GetZoneRequest { - string zone_id = 1; -} - -message GetZoneReply { - krata.v1.common.Zone zone = 1; -} - -message ListZonesRequest {} - -message ListZonesReply { - repeated krata.v1.common.Zone zones = 1; -} - -message ExecInsideZoneRequest { - string zone_id = 1; - krata.v1.common.ZoneTaskSpec task = 2; - bytes stdin = 3; - bool stdin_closed = 4; - krata.v1.common.TerminalSize terminal_size = 5; -} - -message ExecInsideZoneReply { - bool exited = 1; - string error = 2; - int32 exit_code = 3; - bytes stdout = 4; - bytes stderr = 5; -} - -message ZoneConsoleRequest { - string zone_id = 1; - bytes data = 2; - bool replay_history = 3; -} - -message ZoneConsoleReply { - bytes data = 1; -} - -message WatchEventsRequest {} - -message WatchEventsReply { - oneof event { - ZoneChangedEvent zone_changed = 1; - } -} - -message ZoneChangedEvent { - krata.v1.common.Zone zone = 1; -} - -message ReadZoneMetricsRequest { - string zone_id = 1; -} - -message ReadZoneMetricsReply { - krata.v1.common.ZoneMetricNode root = 1; -} - -message SnoopIdmRequest {} - -message SnoopIdmReply { - string from = 1; - string to = 2; - krata.idm.transport.IdmTransportPacket packet = 3; -} - -message ImageProgress { - ImageProgressPhase phase = 1; - repeated ImageProgressLayer layers = 2; - ImageProgressIndication indication = 3; -} - -enum ImageProgressPhase { - IMAGE_PROGRESS_PHASE_UNKNOWN = 0; - IMAGE_PROGRESS_PHASE_STARTED = 1; - IMAGE_PROGRESS_PHASE_RESOLVING = 2; - IMAGE_PROGRESS_PHASE_RESOLVED = 3; - IMAGE_PROGRESS_PHASE_CONFIG_DOWNLOAD = 4; - IMAGE_PROGRESS_PHASE_LAYER_DOWNLOAD = 5; - IMAGE_PROGRESS_PHASE_ASSEMBLE = 6; - IMAGE_PROGRESS_PHASE_PACK = 7; - IMAGE_PROGRESS_PHASE_COMPLETE = 8; -} - -message ImageProgressLayer { - string id = 1; - ImageProgressLayerPhase phase = 2; - ImageProgressIndication indication = 3; -} - -enum ImageProgressLayerPhase { - IMAGE_PROGRESS_LAYER_PHASE_UNKNOWN = 0; - IMAGE_PROGRESS_LAYER_PHASE_WAITING = 1; - IMAGE_PROGRESS_LAYER_PHASE_DOWNLOADING = 2; - IMAGE_PROGRESS_LAYER_PHASE_DOWNLOADED = 3; - IMAGE_PROGRESS_LAYER_PHASE_EXTRACTING = 4; - IMAGE_PROGRESS_LAYER_PHASE_EXTRACTED = 5; -} - -message ImageProgressIndication { - oneof indication { - ImageProgressIndicationBar bar = 1; - ImageProgressIndicationSpinner spinner = 2; - ImageProgressIndicationHidden hidden = 3; - ImageProgressIndicationCompleted completed = 4; - } -} - -message ImageProgressIndicationBar { - string message = 1; - uint64 current = 2; - uint64 total = 3; - bool is_bytes = 4; -} - -message ImageProgressIndicationSpinner { - string message = 1; -} - -message ImageProgressIndicationHidden {} - -message ImageProgressIndicationCompleted { - string message = 1; - uint64 total = 2; - bool is_bytes = 3; -} - -message PullImageRequest { - string image = 1; - krata.v1.common.OciImageFormat format = 2; - bool overwrite_cache = 3; - bool update = 4; -} - -message PullImageReply { - ImageProgress progress = 1; - string digest = 2; - krata.v1.common.OciImageFormat format = 3; -} - -message DeviceInfo { - string name = 1; - bool claimed = 2; - string owner = 3; -} - -message ListDevicesRequest {} - -message ListDevicesReply { - repeated DeviceInfo devices = 1; -} - -enum HostCpuTopologyClass { - HOST_CPU_TOPOLOGY_CLASS_STANDARD = 0; - HOST_CPU_TOPOLOGY_CLASS_PERFORMANCE = 1; - HOST_CPU_TOPOLOGY_CLASS_EFFICIENCY = 2; -} - -message HostCpuTopologyInfo { - uint32 core = 1; - uint32 socket = 2; - uint32 node = 3; - uint32 thread = 4; - HostCpuTopologyClass class = 5; -} - -message GetHostCpuTopologyRequest {} - -message GetHostCpuTopologyReply { - repeated HostCpuTopologyInfo cpus = 1; -} - -message SetHostPowerManagementPolicyRequest { - string scheduler = 1; - bool smt_awareness = 2; -} - -message SetHostPowerManagementPolicyReply {} - -message UpdateZoneResourcesRequest { - string zone_id = 1; - krata.v1.common.ZoneResourceSpec resources = 2; -} - -message UpdateZoneResourcesReply {} - -message ReadHypervisorConsoleRequest {} - -message ReadHypervisorConsoleReply { - string data = 1; -} - -message ListNetworkReservationsRequest {} - -message ListNetworkReservationsReply { - repeated krata.v1.common.NetworkReservation reservations = 1; -} diff --git a/crates/krata/src/client.rs b/crates/krata/src/client.rs deleted file mode 100644 index a9304050..00000000 --- a/crates/krata/src/client.rs +++ /dev/null @@ -1,54 +0,0 @@ -#[cfg(unix)] -use crate::unix::HyperUnixConnector; -use crate::{dial::ControlDialAddress, v1::control::control_service_client::ControlServiceClient}; -#[cfg(not(unix))] -use anyhow::anyhow; -use anyhow::Result; -use tonic::transport::{Channel, ClientTlsConfig, Endpoint}; - -pub struct ControlClientProvider {} - -impl ControlClientProvider { - pub async fn dial(addr: ControlDialAddress) -> Result> { - let channel = match addr { - ControlDialAddress::UnixSocket { path } => { - #[cfg(not(unix))] - return Err(anyhow!( - "unix sockets are not supported on this platform (path {})", - path - )); - #[cfg(unix)] - ControlClientProvider::dial_unix_socket(path).await? - } - - ControlDialAddress::Tcp { host, port } => { - Endpoint::try_from(format!("http://{}:{}", host, port))? - .connect() - .await? - } - - ControlDialAddress::Tls { - host, - port, - insecure: _, - } => { - let tls_config = ClientTlsConfig::new().domain_name(&host); - let address = format!("https://{}:{}", host, port); - Channel::from_shared(address)? - .tls_config(tls_config)? - .connect() - .await? - } - }; - - Ok(ControlServiceClient::new(channel)) - } - - #[cfg(unix)] - async fn dial_unix_socket(path: String) -> Result { - // This URL is not actually used but is required to be specified. - Ok(Endpoint::try_from(format!("unix://localhost/{}", path))? - .connect_with_connector(HyperUnixConnector {}) - .await?) - } -} diff --git a/crates/krata/src/dial.rs b/crates/krata/src/dial.rs deleted file mode 100644 index 4f44c39e..00000000 --- a/crates/krata/src/dial.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::{fmt::Display, str::FromStr}; - -use anyhow::anyhow; -use url::{Host, Url}; - -pub const KRATA_DEFAULT_TCP_PORT: u16 = 4350; -pub const KRATA_DEFAULT_TLS_PORT: u16 = 4353; - -#[derive(Clone)] -pub enum ControlDialAddress { - UnixSocket { - path: String, - }, - Tcp { - host: String, - port: u16, - }, - Tls { - host: String, - port: u16, - insecure: bool, - }, -} - -impl FromStr for ControlDialAddress { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - let url: Url = s.parse()?; - - let host = url.host().unwrap_or(Host::Domain("localhost")).to_string(); - - match url.scheme() { - "unix" => Ok(ControlDialAddress::UnixSocket { - path: url.path().to_string(), - }), - - "tcp" => { - let port = url.port().unwrap_or(KRATA_DEFAULT_TCP_PORT); - Ok(ControlDialAddress::Tcp { host, port }) - } - - "tls" | "tls-insecure" => { - let insecure = url.scheme() == "tls-insecure"; - let port = url.port().unwrap_or(KRATA_DEFAULT_TLS_PORT); - Ok(ControlDialAddress::Tls { - host, - port, - insecure, - }) - } - - _ => Err(anyhow!("unknown control address scheme: {}", url.scheme())), - } - } -} - -impl From for Url { - fn from(val: ControlDialAddress) -> Self { - match val { - ControlDialAddress::UnixSocket { path } => { - let mut url = Url::parse("unix:///").unwrap(); - url.set_path(&path); - url - } - - ControlDialAddress::Tcp { host, port } => { - let mut url = Url::parse("tcp://").unwrap(); - url.set_host(Some(&host)).unwrap(); - if port != KRATA_DEFAULT_TCP_PORT { - url.set_port(Some(port)).unwrap(); - } - url - } - - ControlDialAddress::Tls { - host, - port, - insecure, - } => { - let mut url = Url::parse("tls://").unwrap(); - if insecure { - url.set_scheme("tls-insecure").unwrap(); - } - url.set_host(Some(&host)).unwrap(); - if port != KRATA_DEFAULT_TLS_PORT { - url.set_port(Some(port)).unwrap(); - } - url - } - } - } -} - -impl Display for ControlDialAddress { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let url: Url = self.clone().into(); - write!(f, "{}", url) - } -} diff --git a/crates/krata/src/ethtool.rs b/crates/krata/src/ethtool.rs deleted file mode 100644 index d6f15358..00000000 --- a/crates/krata/src/ethtool.rs +++ /dev/null @@ -1,81 +0,0 @@ -use std::{ - os::fd::{AsRawFd, FromRawFd, OwnedFd}, - ptr::addr_of_mut, -}; - -use anyhow::Result; -use libc::{ioctl, socket, AF_INET, SOCK_DGRAM}; - -#[repr(C)] -struct EthtoolValue { - cmd: u32, - data: u32, -} - -const ETHTOOL_SGSO: u32 = 0x00000024; -const ETHTOOL_STSO: u32 = 0x0000001f; - -#[cfg(not(target_env = "musl"))] -const SIOCETHTOOL: libc::c_ulong = libc::SIOCETHTOOL; -#[cfg(target_env = "musl")] -const SIOCETHTOOL: libc::c_int = libc::SIOCETHTOOL as i32; - -#[repr(C)] -#[derive(Debug)] -struct EthtoolIfreq { - ifr_name: [libc::c_char; libc::IF_NAMESIZE], - ifr_data: libc::uintptr_t, -} - -impl EthtoolIfreq { - fn new(interface: &str) -> EthtoolIfreq { - let mut ifreq = EthtoolIfreq { - ifr_name: [0; libc::IF_NAMESIZE], - ifr_data: 0, - }; - for (i, byte) in interface.as_bytes().iter().enumerate() { - ifreq.ifr_name[i] = *byte as libc::c_char - } - ifreq - } - - fn set_value(&mut self, ptr: *mut libc::c_void) { - self.ifr_data = ptr as libc::uintptr_t; - } -} - -pub struct EthtoolHandle { - fd: OwnedFd, -} - -impl EthtoolHandle { - pub fn new() -> Result { - let fd = unsafe { socket(AF_INET, SOCK_DGRAM, 0) }; - if fd == -1 { - return Err(std::io::Error::last_os_error().into()); - } - - Ok(EthtoolHandle { - fd: unsafe { OwnedFd::from_raw_fd(fd) }, - }) - } - - pub fn set_gso(&mut self, interface: &str, value: bool) -> Result<()> { - self.set_value(interface, ETHTOOL_SGSO, if value { 1 } else { 0 }) - } - - pub fn set_tso(&mut self, interface: &str, value: bool) -> Result<()> { - self.set_value(interface, ETHTOOL_STSO, if value { 1 } else { 0 }) - } - - fn set_value(&mut self, interface: &str, cmd: u32, value: u32) -> Result<()> { - let mut ifreq = EthtoolIfreq::new(interface); - let mut value = EthtoolValue { cmd, data: value }; - ifreq.set_value(addr_of_mut!(value) as *mut libc::c_void); - let result = unsafe { ioctl(self.fd.as_raw_fd(), SIOCETHTOOL, addr_of_mut!(ifreq) as u64) }; - if result == -1 { - return Err(std::io::Error::last_os_error().into()); - } - Ok(()) - } -} diff --git a/crates/krata/src/events.rs b/crates/krata/src/events.rs deleted file mode 100644 index f31cf6d8..00000000 --- a/crates/krata/src/events.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::{sync::Arc, time::Duration}; - -use crate::v1::control::{ - control_service_client::ControlServiceClient, watch_events_reply::Event, WatchEventsReply, - WatchEventsRequest, -}; -use anyhow::Result; -use log::{error, trace, warn}; -use tokio::{sync::broadcast, task::JoinHandle, time::sleep}; -use tokio_stream::StreamExt; -use tonic::{transport::Channel, Streaming}; - -#[derive(Clone)] -pub struct EventStream { - sender: Arc>, - task: Arc>, -} - -impl EventStream { - pub async fn open(client: ControlServiceClient) -> Result { - let (sender, _) = broadcast::channel(1000); - let emit = sender.clone(); - let task = tokio::task::spawn(async move { - if let Err(error) = EventStream::process(client, emit).await { - error!("failed to process event stream: {}", error); - } - }); - Ok(Self { - sender: Arc::new(sender), - task: Arc::new(task), - }) - } - - async fn process( - mut client: ControlServiceClient, - emit: broadcast::Sender, - ) -> Result<()> { - let mut events: Option> = None; - loop { - let mut stream = match events { - Some(stream) => stream, - None => { - let result = client.watch_events(WatchEventsRequest {}).await; - if let Err(error) = result { - warn!("failed to watch events: {}", error); - sleep(Duration::from_secs(1)).await; - continue; - } - result.unwrap().into_inner() - } - }; - - let Some(result) = stream.next().await else { - events = None; - continue; - }; - - let reply = match result { - Ok(reply) => reply, - Err(error) => { - trace!("event stream processing failed: {}", error); - events = None; - continue; - } - }; - - let Some(event) = reply.event else { - events = Some(stream); - continue; - }; - let _ = emit.send(event); - events = Some(stream); - } - } - - pub fn subscribe(&self) -> broadcast::Receiver { - self.sender.subscribe() - } -} - -impl Drop for EventStream { - fn drop(&mut self) { - if Arc::strong_count(&self.task) <= 1 { - self.task.abort(); - } - } -} diff --git a/crates/krata/src/idm/client.rs b/crates/krata/src/idm/client.rs deleted file mode 100644 index 8e8c79f7..00000000 --- a/crates/krata/src/idm/client.rs +++ /dev/null @@ -1,542 +0,0 @@ -use std::{ - collections::HashMap, - path::Path, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - time::Duration, -}; - -use anyhow::{anyhow, Result}; -use bytes::{Buf, BufMut, BytesMut}; -use log::{debug, error}; -use nix::sys::termios::{cfmakeraw, tcgetattr, tcsetattr, SetArg}; -use prost::Message; -use tokio::{ - fs::File, - io::{AsyncReadExt, AsyncWriteExt}, - select, - sync::{ - broadcast, - mpsc::{self, Receiver, Sender}, - oneshot, Mutex, - }, - task::JoinHandle, - time::timeout, -}; - -use super::{ - internal, - serialize::{IdmRequest, IdmSerializable}, - transport::{IdmTransportPacket, IdmTransportPacketForm}, -}; - -type OneshotRequestMap = Arc::Response>>>>; -type StreamRequestMap = Arc::Response>>>>; -type StreamRequestUpdateMap = Arc>>>; -pub type IdmInternalClient = IdmClient; - -const IDM_PACKET_QUEUE_LEN: usize = 100; -const IDM_REQUEST_TIMEOUT_SECS: u64 = 10; -const IDM_PACKET_MAX_SIZE: usize = 20 * 1024 * 1024; - -#[async_trait::async_trait] -pub trait IdmBackend: Send { - async fn recv(&mut self) -> Result>; - async fn send(&mut self, packet: IdmTransportPacket) -> Result<()>; -} - -pub struct IdmFileBackend { - read: Arc>, - read_buffer: BytesMut, - write: Arc>, -} - -impl IdmFileBackend { - pub async fn new(read_file: File, write_file: File) -> Result { - IdmFileBackend::set_raw_port(&read_file)?; - IdmFileBackend::set_raw_port(&write_file)?; - Ok(IdmFileBackend { - read: Arc::new(Mutex::new(read_file)), - read_buffer: BytesMut::new(), - write: Arc::new(Mutex::new(write_file)), - }) - } - - fn set_raw_port(file: &File) -> Result<()> { - let mut termios = tcgetattr(file)?; - cfmakeraw(&mut termios); - tcsetattr(file, SetArg::TCSANOW, &termios)?; - Ok(()) - } -} - -#[async_trait::async_trait] -impl IdmBackend for IdmFileBackend { - async fn recv(&mut self) -> Result> { - let mut data = vec![0; 8192]; - let mut first = true; - 'read_more: loop { - let mut packets = Vec::new(); - if !first { - if !packets.is_empty() { - return Ok(packets); - } - let size = self.read.lock().await.read(&mut data).await?; - self.read_buffer.extend_from_slice(&data[0..size]); - } - first = false; - loop { - if self.read_buffer.len() < 6 { - continue 'read_more; - } - - let b1 = self.read_buffer[0]; - let b2 = self.read_buffer[1]; - - if b1 != 0xff || b2 != 0xff { - self.read_buffer.clear(); - continue 'read_more; - } - - let size = (self.read_buffer[2] as u32 - | (self.read_buffer[3] as u32) << 8 - | (self.read_buffer[4] as u32) << 16 - | (self.read_buffer[5] as u32) << 24) as usize; - let needed = size + 6; - if self.read_buffer.len() < needed { - continue 'read_more; - } - - let mut packet = self.read_buffer.split_to(needed); - packet.advance(6); - - match IdmTransportPacket::decode(packet) { - Ok(packet) => { - packets.push(packet); - } - Err(error) => { - return Err(anyhow!("received invalid idm packet: {}", error)); - } - } - - if self.read_buffer.is_empty() { - break; - } - } - return Ok(packets); - } - } - - async fn send(&mut self, packet: IdmTransportPacket) -> Result<()> { - let mut file = self.write.lock().await; - let length = packet.encoded_len(); - let mut buffer = BytesMut::with_capacity(6 + length); - buffer.put_slice(&[0xff, 0xff]); - buffer.put_u32_le(length as u32); - packet.encode(&mut buffer)?; - file.write_all(&buffer).await?; - Ok(()) - } -} - -#[derive(Clone)] -pub struct IdmClient { - channel: u64, - request_backend_sender: broadcast::Sender<(u64, R)>, - request_stream_backend_sender: broadcast::Sender>, - next_request_id: Arc>, - event_receiver_sender: broadcast::Sender, - tx_sender: Sender, - requests: OneshotRequestMap, - request_streams: StreamRequestMap, - task: Arc>, -} - -impl Drop for IdmClient { - fn drop(&mut self) { - if Arc::strong_count(&self.task) <= 1 { - self.task.abort(); - } - } -} - -pub struct IdmClientStreamRequestHandle { - pub id: u64, - pub receiver: Receiver, - pub client: IdmClient, -} - -impl IdmClientStreamRequestHandle { - pub async fn update(&self, request: R) -> Result<()> { - self.client - .tx_sender - .send(IdmTransportPacket { - id: self.id, - channel: self.client.channel, - form: IdmTransportPacketForm::StreamRequestUpdate.into(), - data: request.encode()?, - }) - .await?; - Ok(()) - } -} - -impl Drop for IdmClientStreamRequestHandle { - fn drop(&mut self) { - let id = self.id; - let client = self.client.clone(); - tokio::task::spawn(async move { - let _ = client - .tx_sender - .send(IdmTransportPacket { - id, - channel: client.channel, - form: IdmTransportPacketForm::StreamRequestClosed.into(), - data: vec![], - }) - .await; - }); - } -} - -#[derive(Clone)] -pub struct IdmClientStreamResponseHandle { - pub initial: R, - pub id: u64, - channel: u64, - tx_sender: Sender, - receiver: Arc>>>, -} - -impl IdmClientStreamResponseHandle { - pub async fn respond(&self, response: R::Response) -> Result<()> { - self.tx_sender - .send(IdmTransportPacket { - id: self.id, - channel: self.channel, - form: IdmTransportPacketForm::StreamResponseUpdate.into(), - data: response.encode()?, - }) - .await?; - Ok(()) - } - - pub async fn take(&self) -> Result> { - let mut guard = self.receiver.lock().await; - let Some(receiver) = (*guard).take() else { - return Err(anyhow!("request has already been claimed!")); - }; - Ok(receiver) - } -} - -impl Drop for IdmClientStreamResponseHandle { - fn drop(&mut self) { - if Arc::strong_count(&self.receiver) <= 1 { - let id = self.id; - let channel = self.channel; - let tx_sender = self.tx_sender.clone(); - tokio::task::spawn(async move { - let _ = tx_sender - .send(IdmTransportPacket { - id, - channel, - form: IdmTransportPacketForm::StreamResponseClosed.into(), - data: vec![], - }) - .await; - }); - } - } -} - -impl IdmClient { - pub async fn new(channel: u64, backend: Box) -> Result { - let requests = Arc::new(Mutex::new(HashMap::new())); - let request_streams = Arc::new(Mutex::new(HashMap::new())); - let request_update_streams = Arc::new(Mutex::new(HashMap::new())); - let (event_sender, event_receiver) = broadcast::channel(IDM_PACKET_QUEUE_LEN); - let (internal_request_backend_sender, _) = broadcast::channel(IDM_PACKET_QUEUE_LEN); - let (internal_request_stream_backend_sender, _) = broadcast::channel(IDM_PACKET_QUEUE_LEN); - let (tx_sender, tx_receiver) = mpsc::channel(IDM_PACKET_QUEUE_LEN); - let backend_event_sender = event_sender.clone(); - let request_backend_sender = internal_request_backend_sender.clone(); - let request_stream_backend_sender = internal_request_stream_backend_sender.clone(); - let requests_for_client = requests.clone(); - let request_streams_for_client = request_streams.clone(); - let tx_sender_for_client = tx_sender.clone(); - let task = tokio::task::spawn(async move { - if let Err(error) = IdmClient::process( - backend, - channel, - tx_sender, - backend_event_sender, - requests, - request_streams, - request_update_streams, - internal_request_backend_sender, - internal_request_stream_backend_sender, - event_receiver, - tx_receiver, - ) - .await - { - debug!("failed to handle idm client processing: {}", error); - } - }); - Ok(IdmClient { - channel, - next_request_id: Arc::new(Mutex::new(0)), - event_receiver_sender: event_sender.clone(), - request_backend_sender, - request_stream_backend_sender, - requests: requests_for_client, - request_streams: request_streams_for_client, - tx_sender: tx_sender_for_client, - task: Arc::new(task), - }) - } - - pub async fn open>(channel: u64, path: P) -> Result { - let read_file = File::options() - .read(true) - .write(false) - .create(false) - .open(&path) - .await?; - let write_file = File::options() - .read(false) - .write(true) - .create(false) - .open(path) - .await?; - let backend = IdmFileBackend::new(read_file, write_file).await?; - IdmClient::new(channel, Box::new(backend) as Box).await - } - - pub async fn emit(&self, event: T) -> Result<()> { - let id = { - let mut guard = self.next_request_id.lock().await; - let req = *guard; - *guard = req.wrapping_add(1); - req - }; - self.tx_sender - .send(IdmTransportPacket { - id, - form: IdmTransportPacketForm::Event.into(), - channel: self.channel, - data: event.encode()?, - }) - .await?; - Ok(()) - } - - pub async fn requests(&self) -> Result> { - Ok(self.request_backend_sender.subscribe()) - } - - pub async fn request_streams( - &self, - ) -> Result>> { - Ok(self.request_stream_backend_sender.subscribe()) - } - - pub async fn respond(&self, id: u64, response: T) -> Result<()> { - let packet = IdmTransportPacket { - id, - form: IdmTransportPacketForm::Response.into(), - channel: self.channel, - data: response.encode()?, - }; - self.tx_sender.send(packet).await?; - Ok(()) - } - - pub async fn subscribe(&self) -> Result> { - Ok(self.event_receiver_sender.subscribe()) - } - - pub async fn send(&self, request: R) -> Result { - let (sender, receiver) = oneshot::channel::(); - let req = { - let mut guard = self.next_request_id.lock().await; - let req = *guard; - *guard = req.wrapping_add(1); - req - }; - let mut requests = self.requests.lock().await; - requests.insert(req, sender); - drop(requests); - let success = AtomicBool::new(false); - let _guard = scopeguard::guard(self.requests.clone(), |requests| { - if success.load(Ordering::Acquire) { - return; - } - tokio::task::spawn(async move { - let mut requests = requests.lock().await; - requests.remove(&req); - }); - }); - self.tx_sender - .send(IdmTransportPacket { - id: req, - channel: self.channel, - form: IdmTransportPacketForm::Request.into(), - data: request.encode()?, - }) - .await?; - - let response = timeout(Duration::from_secs(IDM_REQUEST_TIMEOUT_SECS), receiver).await??; - success.store(true, Ordering::Release); - Ok(response) - } - - pub async fn send_stream(&self, request: R) -> Result> { - let (sender, receiver) = mpsc::channel::(100); - let req = { - let mut guard = self.next_request_id.lock().await; - let req = *guard; - *guard = req.wrapping_add(1); - req - }; - let mut requests = self.request_streams.lock().await; - requests.insert(req, sender); - drop(requests); - self.tx_sender - .send(IdmTransportPacket { - id: req, - channel: self.channel, - form: IdmTransportPacketForm::StreamRequest.into(), - data: request.encode()?, - }) - .await?; - Ok(IdmClientStreamRequestHandle { - id: req, - receiver, - client: self.clone(), - }) - } - - #[allow(clippy::too_many_arguments)] - async fn process( - mut backend: Box, - channel: u64, - tx_sender: Sender, - event_sender: broadcast::Sender, - requests: OneshotRequestMap, - request_streams: StreamRequestMap, - request_update_streams: StreamRequestUpdateMap, - request_backend_sender: broadcast::Sender<(u64, R)>, - request_stream_backend_sender: broadcast::Sender>, - _event_receiver: broadcast::Receiver, - mut receiver: Receiver, - ) -> Result<()> { - loop { - select! { - x = backend.recv() => match x { - Ok(packets) => { - for packet in packets { - if packet.channel != channel { - continue; - } - - match packet.form() { - IdmTransportPacketForm::Event => { - if let Ok(event) = E::decode(&packet.data) { - let _ = event_sender.send(event); - } - }, - - IdmTransportPacketForm::Request => { - if let Ok(request) = R::decode(&packet.data) { - let _ = request_backend_sender.send((packet.id, request)); - } - }, - - IdmTransportPacketForm::Response => { - let mut requests = requests.lock().await; - if let Some(sender) = requests.remove(&packet.id) { - drop(requests); - - if let Ok(response) = R::Response::decode(&packet.data) { - let _ = sender.send(response); - } - } - }, - - IdmTransportPacketForm::StreamRequest => { - if let Ok(request) = R::decode(&packet.data) { - let mut update_streams = request_update_streams.lock().await; - let (sender, receiver) = mpsc::channel(100); - update_streams.insert(packet.id, sender.clone()); - let handle = IdmClientStreamResponseHandle { - initial: request, - id: packet.id, - channel, - tx_sender: tx_sender.clone(), - receiver: Arc::new(Mutex::new(Some(receiver))), - }; - let _ = request_stream_backend_sender.send(handle); - } - } - - IdmTransportPacketForm::StreamRequestUpdate => { - if let Ok(request) = R::decode(&packet.data) { - let mut update_streams = request_update_streams.lock().await; - if let Some(stream) = update_streams.get_mut(&packet.id) { - let _ = stream.try_send(request); - } - } - } - - IdmTransportPacketForm::StreamRequestClosed => { - let mut update_streams = request_update_streams.lock().await; - update_streams.remove(&packet.id); - println!("stream request closed: {}", packet.id); - } - - IdmTransportPacketForm::StreamResponseUpdate => { - let requests = request_streams.lock().await; - if let Some(sender) = requests.get(&packet.id) { - if let Ok(response) = R::Response::decode(&packet.data) { - let _ = sender.try_send(response); - } - } - } - - IdmTransportPacketForm::StreamResponseClosed => { - let mut requests = request_streams.lock().await; - requests.remove(&packet.id); - } - - _ => {}, - } - } - }, - - Err(error) => { - return Err(anyhow!("failed to read idm client: {}", error)); - } - }, - x = receiver.recv() => match x { - Some(packet) => { - let length = packet.encoded_len(); - if length > IDM_PACKET_MAX_SIZE { - error!("unable to send idm packet, packet size exceeded (tried to send {} bytes)", length); - continue; - } - backend.send(packet.clone()).await?; - }, - - None => { - break; - } - } - } - } - Ok(()) - } -} diff --git a/crates/krata/src/idm/internal.rs b/crates/krata/src/idm/internal.rs deleted file mode 100644 index a6ef6eeb..00000000 --- a/crates/krata/src/idm/internal.rs +++ /dev/null @@ -1,129 +0,0 @@ -use anyhow::Result; -use prost::Message; -use prost_types::{ListValue, Value}; - -use super::serialize::{IdmRequest, IdmSerializable}; - -include!(concat!(env!("OUT_DIR"), "/krata.idm.internal.rs")); - -pub const INTERNAL_IDM_CHANNEL: u64 = 0; - -impl IdmSerializable for Event { - fn encode(&self) -> Result> { - Ok(self.encode_to_vec()) - } - - fn decode(bytes: &[u8]) -> Result { - Ok(::decode(bytes)?) - } -} - -impl IdmSerializable for Request { - fn encode(&self) -> Result> { - Ok(self.encode_to_vec()) - } - - fn decode(bytes: &[u8]) -> Result { - Ok(::decode(bytes)?) - } -} - -impl IdmRequest for Request { - type Response = Response; -} - -impl IdmSerializable for Response { - fn encode(&self) -> Result> { - Ok(self.encode_to_vec()) - } - - fn decode(bytes: &[u8]) -> Result { - Ok(::decode(bytes)?) - } -} - -pub trait AsIdmMetricValue { - fn as_metric_value(&self) -> Value; -} - -impl MetricNode { - pub fn structural>(name: N, children: Vec) -> MetricNode { - MetricNode { - name: name.as_ref().to_string(), - value: None, - format: MetricFormat::Unknown.into(), - children, - } - } - - pub fn raw_value, V: AsIdmMetricValue>(name: N, value: V) -> MetricNode { - MetricNode { - name: name.as_ref().to_string(), - value: Some(value.as_metric_value()), - format: MetricFormat::Unknown.into(), - children: vec![], - } - } - - pub fn value, V: AsIdmMetricValue>( - name: N, - value: V, - format: MetricFormat, - ) -> MetricNode { - MetricNode { - name: name.as_ref().to_string(), - value: Some(value.as_metric_value()), - format: format.into(), - children: vec![], - } - } -} - -impl AsIdmMetricValue for String { - fn as_metric_value(&self) -> Value { - Value { - kind: Some(prost_types::value::Kind::StringValue(self.to_string())), - } - } -} - -impl AsIdmMetricValue for &str { - fn as_metric_value(&self) -> Value { - Value { - kind: Some(prost_types::value::Kind::StringValue(self.to_string())), - } - } -} - -impl AsIdmMetricValue for u64 { - fn as_metric_value(&self) -> Value { - numeric(*self as f64) - } -} - -impl AsIdmMetricValue for i64 { - fn as_metric_value(&self) -> Value { - numeric(*self as f64) - } -} - -impl AsIdmMetricValue for f64 { - fn as_metric_value(&self) -> Value { - numeric(*self) - } -} - -impl AsIdmMetricValue for Vec { - fn as_metric_value(&self) -> Value { - let values = self.iter().map(|x| x.as_metric_value()).collect::<_>(); - Value { - kind: Some(prost_types::value::Kind::ListValue(ListValue { values })), - } - } -} - -fn numeric(value: f64) -> Value { - Value { - kind: Some(prost_types::value::Kind::NumberValue(value)), - } -} diff --git a/crates/krata/src/idm/mod.rs b/crates/krata/src/idm/mod.rs deleted file mode 100644 index b02969e2..00000000 --- a/crates/krata/src/idm/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[cfg(unix)] -pub mod client; -pub mod internal; -pub mod serialize; -pub mod transport; diff --git a/crates/krata/src/idm/serialize.rs b/crates/krata/src/idm/serialize.rs deleted file mode 100644 index 519527c0..00000000 --- a/crates/krata/src/idm/serialize.rs +++ /dev/null @@ -1,10 +0,0 @@ -use anyhow::Result; - -pub trait IdmSerializable: Sized + Clone + Send + Sync + 'static { - fn decode(bytes: &[u8]) -> Result; - fn encode(&self) -> Result>; -} - -pub trait IdmRequest: IdmSerializable { - type Response: IdmSerializable; -} diff --git a/crates/krata/src/idm/transport.rs b/crates/krata/src/idm/transport.rs deleted file mode 100644 index 65b8c2dc..00000000 --- a/crates/krata/src/idm/transport.rs +++ /dev/null @@ -1 +0,0 @@ -include!(concat!(env!("OUT_DIR"), "/krata.idm.transport.rs")); diff --git a/crates/krata/src/launchcfg.rs b/crates/krata/src/launchcfg.rs deleted file mode 100644 index 60e1f265..00000000 --- a/crates/krata/src/launchcfg.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::collections::HashMap; - -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum LaunchPackedFormat { - Squashfs, - Erofs, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct LaunchNetworkIpv4 { - pub address: String, - pub gateway: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct LaunchNetworkIpv6 { - pub address: String, - pub gateway: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct LaunchNetworkResolver { - pub nameservers: Vec, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct LaunchNetwork { - pub link: String, - pub ipv4: LaunchNetworkIpv4, - pub ipv6: LaunchNetworkIpv6, - pub resolver: LaunchNetworkResolver, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct LaunchRoot { - pub format: LaunchPackedFormat, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct LaunchInfo { - pub root: LaunchRoot, - pub hostname: Option, - pub network: Option, - pub env: HashMap, - pub run: Option>, -} diff --git a/crates/krata/src/lib.rs b/crates/krata/src/lib.rs deleted file mode 100644 index 149e24ab..00000000 --- a/crates/krata/src/lib.rs +++ /dev/null @@ -1,23 +0,0 @@ -use once_cell::sync::Lazy; -use prost_reflect::DescriptorPool; - -pub mod v1; - -pub mod client; -pub mod dial; -pub mod events; -pub mod idm; -pub mod launchcfg; - -#[cfg(target_os = "linux")] -pub mod ethtool; - -#[cfg(unix)] -pub mod unix; - -pub static DESCRIPTOR_POOL: Lazy = Lazy::new(|| { - DescriptorPool::decode( - include_bytes!(concat!(env!("OUT_DIR"), "/file_descriptor_set.bin")).as_ref(), - ) - .unwrap() -}); diff --git a/crates/krata/src/unix.rs b/crates/krata/src/unix.rs deleted file mode 100644 index 1bd0f60b..00000000 --- a/crates/krata/src/unix.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::future::Future; -use std::io::Error; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use hyper::rt::ReadBufCursor; -use hyper_util::rt::TokioIo; -use pin_project_lite::pin_project; -use tokio::io::AsyncWrite; -use tokio::net::UnixStream; -use tonic::transport::Uri; -use tower::Service; - -pin_project! { - #[derive(Debug)] - pub struct HyperUnixStream { - #[pin] - pub stream: UnixStream, - } -} - -impl hyper::rt::Read for HyperUnixStream { - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: ReadBufCursor<'_>, - ) -> Poll> { - let mut tokio = TokioIo::new(self.project().stream); - Pin::new(&mut tokio).poll_read(cx, buf) - } -} - -impl hyper::rt::Write for HyperUnixStream { - fn poll_write( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - self.project().stream.poll_write(cx, buf) - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().stream.poll_flush(cx) - } - - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().stream.poll_shutdown(cx) - } -} - -pub struct HyperUnixConnector; - -impl Service for HyperUnixConnector { - type Response = HyperUnixStream; - type Error = Error; - #[allow(clippy::type_complexity)] - type Future = - Pin> + Send + 'static>>; - - fn call(&mut self, req: Uri) -> Self::Future { - let fut = async move { - let path = req.path().to_string(); - let stream = UnixStream::connect(path).await?; - Ok(HyperUnixStream { stream }) - }; - - Box::pin(fut) - } - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } -} diff --git a/crates/krata/src/v1/common.rs b/crates/krata/src/v1/common.rs deleted file mode 100644 index e9484ad3..00000000 --- a/crates/krata/src/v1/common.rs +++ /dev/null @@ -1,2 +0,0 @@ -#![allow(clippy::all)] -tonic::include_proto!("krata.v1.common"); diff --git a/crates/krata/src/v1/control.rs b/crates/krata/src/v1/control.rs deleted file mode 100644 index 56b67f72..00000000 --- a/crates/krata/src/v1/control.rs +++ /dev/null @@ -1,2 +0,0 @@ -#![allow(clippy::all)] -tonic::include_proto!("krata.v1.control"); diff --git a/crates/krata/src/v1/mod.rs b/crates/krata/src/v1/mod.rs deleted file mode 100644 index 36cc545f..00000000 --- a/crates/krata/src/v1/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod common; -pub mod control; diff --git a/crates/network/Cargo.toml b/crates/network/Cargo.toml deleted file mode 100644 index 10e7b7cd..00000000 --- a/crates/network/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "krata-network" -description = "Networking services for the krata isolation engine" -license.workspace = true -version.workspace = true -homepage.workspace = true -repository.workspace = true -edition = "2021" -resolver = "2" - -[dependencies] -anyhow = { workspace = true } -async-trait = { workspace = true } -bytes = { workspace = true } -clap = { workspace = true } -env_logger = { workspace = true } -etherparse = { workspace = true } -futures = { workspace = true } -krata = { path = "../krata", version = "^0.0.21" } -krata-advmac = { workspace = true } -libc = { workspace = true } -log = { workspace = true } -rtnetlink = { workspace = true } -smoltcp = { workspace = true } -tonic = { workspace = true } -tokio = { workspace = true } -tokio-tun = { workspace = true } -udp-stream = { workspace = true } -uuid = { workspace = true } - -[lib] -name = "kratanet" - -[[bin]] -name = "kratanet" -path = "bin/network.rs" - -[[example]] -name = "ping" -path = "examples/ping.rs" diff --git a/crates/network/bin/network.rs b/crates/network/bin/network.rs deleted file mode 100644 index 68f1359c..00000000 --- a/crates/network/bin/network.rs +++ /dev/null @@ -1,22 +0,0 @@ -use std::str::FromStr; - -use anyhow::Result; -use clap::Parser; -use env_logger::Env; -use krata::dial::ControlDialAddress; -use kratanet::NetworkService; - -#[derive(Parser, Debug)] -struct NetworkArgs { - #[arg(short, long, default_value = "unix:///var/lib/krata/daemon.socket")] - connection: String, -} - -#[tokio::main] -async fn main() -> Result<()> { - env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); - let args = NetworkArgs::parse(); - let control_dial_address = ControlDialAddress::from_str(&args.connection)?; - let mut service = NetworkService::new(control_dial_address).await?; - service.watch().await -} diff --git a/crates/network/examples/ping.rs b/crates/network/examples/ping.rs deleted file mode 100644 index 61bdf0b0..00000000 --- a/crates/network/examples/ping.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::{net::Ipv6Addr, str::FromStr, time::Duration}; - -use anyhow::Result; -use kratanet::icmp::{IcmpClient, IcmpProtocol}; - -#[tokio::main] -async fn main() -> Result<()> { - let client = IcmpClient::new(IcmpProtocol::Icmpv6)?; - let payload: [u8; 4] = [12u8, 14u8, 16u8, 32u8]; - let result = client - .ping6( - Ipv6Addr::from_str("2606:4700:4700::1111")?, - 0, - 1, - &payload, - Duration::from_secs(10), - ) - .await?; - println!("reply: {:?}", result); - Ok(()) -} diff --git a/crates/network/src/autonet.rs b/crates/network/src/autonet.rs deleted file mode 100644 index 69fafa0c..00000000 --- a/crates/network/src/autonet.rs +++ /dev/null @@ -1,198 +0,0 @@ -use anyhow::Result; -use krata::{ - events::EventStream, - v1::{ - common::Zone, - control::{ - control_service_client::ControlServiceClient, watch_events_reply::Event, - ListZonesRequest, - }, - }, -}; -use log::warn; -use smoltcp::wire::{EthernetAddress, Ipv4Cidr, Ipv6Cidr}; -use std::{collections::HashMap, str::FromStr, time::Duration}; -use tokio::{select, sync::broadcast::Receiver, time::sleep}; -use tonic::transport::Channel; -use uuid::Uuid; - -pub struct AutoNetworkWatcher { - control: ControlServiceClient, - pub events: EventStream, - known: HashMap, -} - -#[derive(Debug, Clone)] -pub struct NetworkSide { - pub ipv4: Ipv4Cidr, - pub ipv6: Ipv6Cidr, - pub mac: EthernetAddress, -} - -#[derive(Debug, Clone)] -pub struct NetworkMetadata { - pub domid: u32, - pub uuid: Uuid, - pub zone: NetworkSide, - pub gateway: NetworkSide, -} - -impl NetworkMetadata { - pub fn interface(&self) -> String { - format!("vif{}.20", self.domid) - } -} - -#[derive(Debug, Clone)] -pub struct AutoNetworkChangeset { - pub added: Vec, - pub removed: Vec, -} - -impl AutoNetworkWatcher { - pub async fn new(control: ControlServiceClient) -> Result { - let client = control.clone(); - Ok(AutoNetworkWatcher { - control, - events: EventStream::open(client).await?, - known: HashMap::new(), - }) - } - - pub async fn read(&mut self) -> Result> { - let mut all_zones: HashMap = HashMap::new(); - for zone in self - .control - .list_zones(ListZonesRequest {}) - .await? - .into_inner() - .zones - { - let Ok(uuid) = Uuid::from_str(&zone.id) else { - continue; - }; - all_zones.insert(uuid, zone); - } - - let mut networks: Vec = Vec::new(); - for (uuid, zone) in &all_zones { - let Some(ref status) = zone.status else { - continue; - }; - - if status.domid == u32::MAX { - continue; - } - - let Some(ref network_status) = status.network_status else { - continue; - }; - - let Ok(zone_ipv4_cidr) = Ipv4Cidr::from_str(&network_status.zone_ipv4) else { - continue; - }; - - let Ok(zone_ipv6_cidr) = Ipv6Cidr::from_str(&network_status.zone_ipv6) else { - continue; - }; - - let Ok(zone_mac) = EthernetAddress::from_str(&network_status.zone_mac) else { - continue; - }; - - let Ok(gateway_ipv4_cidr) = Ipv4Cidr::from_str(&network_status.gateway_ipv4) else { - continue; - }; - - let Ok(gateway_ipv6_cidr) = Ipv6Cidr::from_str(&network_status.gateway_ipv6) else { - continue; - }; - - let Ok(gateway_mac) = EthernetAddress::from_str(&network_status.gateway_mac) else { - continue; - }; - - networks.push(NetworkMetadata { - domid: status.domid, - uuid: *uuid, - zone: NetworkSide { - ipv4: zone_ipv4_cidr, - ipv6: zone_ipv6_cidr, - mac: zone_mac, - }, - gateway: NetworkSide { - ipv4: gateway_ipv4_cidr, - ipv6: gateway_ipv6_cidr, - mac: gateway_mac, - }, - }); - } - Ok(networks) - } - - pub async fn read_changes(&mut self) -> Result { - let mut seen: Vec = Vec::new(); - let mut added: Vec = Vec::new(); - let mut removed: Vec = Vec::new(); - - let networks = match self.read().await { - Ok(networks) => networks, - Err(error) => { - warn!("failed to read network changes: {}", error); - return Ok(AutoNetworkChangeset { added, removed }); - } - }; - - for network in networks { - seen.push(network.uuid); - if self.known.contains_key(&network.uuid) { - continue; - } - let _ = self.known.insert(network.uuid, network.clone()); - added.push(network); - } - - let mut gone: Vec = Vec::new(); - for uuid in self.known.keys() { - if seen.contains(uuid) { - continue; - } - gone.push(*uuid); - } - - for uuid in &gone { - let Some(network) = self.known.remove(uuid) else { - continue; - }; - - removed.push(network); - } - - Ok(AutoNetworkChangeset { added, removed }) - } - - pub async fn wait(&mut self, receiver: &mut Receiver) -> Result<()> { - loop { - select! { - x = receiver.recv() => match x { - Ok(Event::ZoneChanged(_)) => { - break; - }, - - Err(error) => { - warn!("failed to receive event: {}", error); - } - }, - - _ = sleep(Duration::from_secs(10)) => { - break; - } - } - } - Ok(()) - } - - pub fn mark_unknown(&mut self, uuid: Uuid) -> Result { - Ok(self.known.remove(&uuid).is_some()) - } -} diff --git a/crates/network/src/backend.rs b/crates/network/src/backend.rs deleted file mode 100644 index 048e36d8..00000000 --- a/crates/network/src/backend.rs +++ /dev/null @@ -1,177 +0,0 @@ -use crate::autonet::NetworkMetadata; -use crate::chandev::ChannelDevice; -use crate::nat::Nat; -use crate::proxynat::ProxyNatHandlerFactory; -use crate::raw_socket::{AsyncRawSocketChannel, RawSocketHandle, RawSocketProtocol}; -use crate::vbridge::{BridgeJoinHandle, VirtualBridge}; -use crate::EXTRA_MTU; -use anyhow::{anyhow, Result}; -use bytes::BytesMut; -use futures::TryStreamExt; -use log::{info, trace, warn}; -use smoltcp::iface::{Config, Interface, SocketSet}; -use smoltcp::phy::Medium; -use smoltcp::time::Instant; -use smoltcp::wire::{HardwareAddress, IpCidr}; -use tokio::select; -use tokio::sync::mpsc::{channel, Receiver}; -use tokio::task::JoinHandle; - -const TX_CHANNEL_BUFFER_LEN: usize = 3000; - -#[derive(Clone)] -pub struct NetworkBackend { - metadata: NetworkMetadata, - bridge: VirtualBridge, -} - -#[derive(Debug)] -enum NetworkStackSelect { - Receive(Option), - Send(Option), -} - -struct NetworkStack<'a> { - tx: Receiver, - kdev: AsyncRawSocketChannel, - udev: ChannelDevice, - interface: Interface, - sockets: SocketSet<'a>, - nat: Nat, - bridge: BridgeJoinHandle, -} - -impl NetworkStack<'_> { - async fn poll(&mut self) -> Result { - let what = select! { - biased; - x = self.kdev.receiver.recv() => NetworkStackSelect::Receive(x), - x = self.tx.recv() => NetworkStackSelect::Send(x), - x = self.bridge.from_bridge_receiver.recv() => NetworkStackSelect::Send(x), - x = self.bridge.from_broadcast_receiver.recv() => NetworkStackSelect::Send(x.ok()), - }; - - match what { - NetworkStackSelect::Receive(Some(packet)) => { - if let Err(error) = self.bridge.to_bridge_sender.try_send(packet.clone()) { - trace!("failed to send zone packet to bridge: {}", error); - } - - if let Err(error) = self.nat.receive_sender.try_send(packet.clone()) { - trace!("failed to send zone packet to nat: {}", error); - } - - self.udev.rx = Some(packet); - self.interface - .poll(Instant::now(), &mut self.udev, &mut self.sockets); - } - - NetworkStackSelect::Send(Some(packet)) => { - if let Err(error) = self.kdev.sender.try_send(packet) { - warn!("failed to transmit packet to interface: {}", error); - } - } - - NetworkStackSelect::Receive(None) | NetworkStackSelect::Send(None) => { - return Ok(false); - } - } - - Ok(true) - } -} - -impl NetworkBackend { - pub fn new(metadata: NetworkMetadata, bridge: VirtualBridge) -> Result { - Ok(Self { metadata, bridge }) - } - - pub async fn init(&mut self) -> Result<()> { - let interface = self.metadata.interface(); - let (connection, handle, _) = rtnetlink::new_connection()?; - tokio::spawn(connection); - - let mut links = handle.link().get().match_name(interface.clone()).execute(); - let link = links.try_next().await?; - if link.is_none() { - return Err(anyhow!( - "unable to find network interface named {}", - interface - )); - } - let link = link.unwrap(); - handle.link().set(link.header.index).up().execute().await?; - Ok(()) - } - - pub async fn run(&self) -> Result<()> { - let mut stack = self.create_network_stack().await?; - loop { - if !stack.poll().await? { - break; - } - } - Ok(()) - } - - async fn create_network_stack(&self) -> Result { - let interface = self.metadata.interface(); - let proxy = Box::new(ProxyNatHandlerFactory::new()); - let addresses: Vec = vec![ - self.metadata.gateway.ipv4.into(), - self.metadata.gateway.ipv6.into(), - ]; - let mut kdev = - RawSocketHandle::bound_to_interface(&interface, RawSocketProtocol::Ethernet)?; - let mtu = kdev.mtu_of_interface(&interface)? + EXTRA_MTU; - let (tx_sender, tx_receiver) = channel::(TX_CHANNEL_BUFFER_LEN); - let mut udev = ChannelDevice::new(mtu, Medium::Ethernet, tx_sender.clone()); - let mac = self.metadata.gateway.mac; - let local_cidrs = addresses.clone(); - let nat = Nat::new(mtu, proxy, mac, local_cidrs, tx_sender.clone())?; - let hardware_addr = HardwareAddress::Ethernet(mac); - let config = Config::new(hardware_addr); - let mut iface = Interface::new(config, &mut udev, Instant::now()); - iface.update_ip_addrs(|addrs| { - addrs - .extend_from_slice(&addresses) - .expect("failed to set ip addresses"); - }); - let sockets = SocketSet::new(vec![]); - let handle = self.bridge.join(self.metadata.zone.mac).await?; - let kdev = AsyncRawSocketChannel::new(mtu, kdev)?; - Ok(NetworkStack { - tx: tx_receiver, - kdev, - udev, - interface: iface, - sockets, - nat, - bridge: handle, - }) - } - - pub async fn launch(self) -> Result> { - Ok(tokio::task::spawn(async move { - info!( - "launched network backend for krata zone {}", - self.metadata.uuid - ); - if let Err(error) = self.run().await { - warn!( - "network backend for krata zone {} failed: {}", - self.metadata.uuid, error - ); - } - })) - } -} - -impl Drop for NetworkBackend { - fn drop(&mut self) { - info!( - "destroyed network backend for krata zone {}", - self.metadata.uuid - ); - } -} diff --git a/crates/network/src/chandev.rs b/crates/network/src/chandev.rs deleted file mode 100644 index c30970bd..00000000 --- a/crates/network/src/chandev.rs +++ /dev/null @@ -1,89 +0,0 @@ -// Referenced https://github.com/vi/wgslirpy/blob/master/crates/libwgslirpy/src/channelized_smoltcp_device.rs -use bytes::BytesMut; -use log::{debug, warn}; -use smoltcp::phy::{Checksum, Device, Medium}; -use tokio::sync::mpsc::Sender; - -const TEAR_OFF_BUFFER_SIZE: usize = 65536; - -pub struct ChannelDevice { - pub mtu: usize, - pub medium: Medium, - pub tx: Sender, - pub rx: Option, - tear_off_buffer: BytesMut, -} - -impl ChannelDevice { - pub fn new(mtu: usize, medium: Medium, tx: Sender) -> Self { - Self { - mtu, - medium, - tx, - rx: None, - tear_off_buffer: BytesMut::with_capacity(TEAR_OFF_BUFFER_SIZE), - } - } -} - -pub struct RxToken(pub BytesMut); - -impl Device for ChannelDevice { - type RxToken<'a> = RxToken where Self: 'a; - type TxToken<'a> = &'a mut ChannelDevice where Self: 'a; - - fn receive( - &mut self, - _timestamp: smoltcp::time::Instant, - ) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { - self.rx.take().map(|x| (RxToken(x), self)) - } - - fn transmit(&mut self, _timestamp: smoltcp::time::Instant) -> Option> { - if self.tx.capacity() == 0 { - debug!("ran out of transmission capacity"); - return None; - } - Some(self) - } - - fn capabilities(&self) -> smoltcp::phy::DeviceCapabilities { - let mut capabilities = smoltcp::phy::DeviceCapabilities::default(); - capabilities.medium = self.medium; - capabilities.max_transmission_unit = self.mtu; - capabilities.checksum = smoltcp::phy::ChecksumCapabilities::ignored(); - capabilities.checksum.tcp = Checksum::Tx; - capabilities.checksum.ipv4 = Checksum::Tx; - capabilities.checksum.icmpv4 = Checksum::Tx; - capabilities.checksum.icmpv6 = Checksum::Tx; - capabilities - } -} - -impl smoltcp::phy::RxToken for RxToken { - fn consume(mut self, f: F) -> R - where - F: FnOnce(&mut [u8]) -> R, - { - f(&mut self.0[..]) - } -} - -impl<'a> smoltcp::phy::TxToken for &'a mut ChannelDevice { - fn consume(self, len: usize, f: F) -> R - where - F: FnOnce(&mut [u8]) -> R, - { - self.tear_off_buffer.resize(len, 0); - let result = f(&mut self.tear_off_buffer[..]); - let chunk = self.tear_off_buffer.split(); - if let Err(error) = self.tx.try_send(chunk) { - warn!("failed to transmit packet: {}", error); - } - - if self.tear_off_buffer.capacity() < self.mtu { - self.tear_off_buffer = BytesMut::with_capacity(TEAR_OFF_BUFFER_SIZE); - } - result - } -} diff --git a/crates/network/src/hbridge.rs b/crates/network/src/hbridge.rs deleted file mode 100644 index 20e11c7d..00000000 --- a/crates/network/src/hbridge.rs +++ /dev/null @@ -1,153 +0,0 @@ -use std::{io::ErrorKind, net::IpAddr}; - -use anyhow::{anyhow, Result}; -use bytes::BytesMut; -use futures::TryStreamExt; -use log::error; -use smoltcp::wire::{EthernetAddress, Ipv4Cidr, Ipv6Cidr}; -use tokio::{select, task::JoinHandle}; -use tokio_tun::Tun; - -use crate::vbridge::{BridgeJoinHandle, VirtualBridge}; - -#[derive(Debug)] -enum HostBridgeProcessSelect { - Send(Option), - Receive(std::io::Result), -} - -pub struct HostBridge { - task: JoinHandle<()>, -} - -impl HostBridge { - pub async fn new( - mtu: usize, - interface: String, - bridge: &VirtualBridge, - ipv4: Ipv4Cidr, - ipv6: Ipv6Cidr, - mac: EthernetAddress, - ) -> Result { - let tun = Tun::builder() - .name(&interface) - .tap(true) - .mtu(mtu as i32) - .packet_info(false) - .try_build()?; - - let (connection, handle, _) = rtnetlink::new_connection()?; - tokio::spawn(connection); - - let mut links = handle.link().get().match_name(interface.clone()).execute(); - let link = links.try_next().await?; - if link.is_none() { - return Err(anyhow!( - "unable to find network interface named {}", - interface - )); - } - let link = link.unwrap(); - - handle - .address() - .add( - link.header.index, - IpAddr::V4(ipv4.address().into()), - ipv4.prefix_len(), - ) - .execute() - .await?; - - handle - .address() - .add( - link.header.index, - IpAddr::V6(ipv6.address().into()), - ipv6.prefix_len(), - ) - .execute() - .await?; - - handle - .link() - .set(link.header.index) - .address(mac.0.to_vec()) - .up() - .execute() - .await?; - - let bridge_handle = bridge.join(mac).await?; - - let task = tokio::task::spawn(async move { - if let Err(error) = HostBridge::process(mtu, tun, bridge_handle).await { - error!("failed to process host bridge: {}", error); - } - }); - - Ok(HostBridge { task }) - } - - async fn process(mtu: usize, tun: Tun, mut bridge_handle: BridgeJoinHandle) -> Result<()> { - let tear_off_size = 100 * mtu; - let mut buffer: BytesMut = BytesMut::with_capacity(tear_off_size); - loop { - if buffer.capacity() < mtu { - buffer = BytesMut::with_capacity(tear_off_size); - } - - buffer.resize(mtu, 0); - let selection = select! { - biased; - x = tun.recv(&mut buffer) => HostBridgeProcessSelect::Receive(x), - x = bridge_handle.from_bridge_receiver.recv() => HostBridgeProcessSelect::Send(x), - x = bridge_handle.from_broadcast_receiver.recv() => HostBridgeProcessSelect::Send(x.ok()), - }; - - match selection { - HostBridgeProcessSelect::Send(Some(bytes)) => match tun.try_send(&bytes) { - Ok(_) => {} - Err(error) => { - if error.kind() == ErrorKind::WouldBlock { - continue; - } - return Err(error.into()); - } - }, - - HostBridgeProcessSelect::Send(None) => { - break; - } - - HostBridgeProcessSelect::Receive(result) => match result { - Ok(len) => { - if len == 0 { - continue; - } - let packet = buffer.split_to(len); - let _ = bridge_handle.to_bridge_sender.try_send(packet); - } - - Err(error) => { - if error.kind() == ErrorKind::WouldBlock { - continue; - } - - error!( - "failed to receive data from tap device to bridge: {}", - error - ); - break; - } - }, - } - } - Ok(()) - } -} - -impl Drop for HostBridge { - fn drop(&mut self) { - self.task.abort(); - } -} diff --git a/crates/network/src/icmp.rs b/crates/network/src/icmp.rs deleted file mode 100644 index e772a838..00000000 --- a/crates/network/src/icmp.rs +++ /dev/null @@ -1,250 +0,0 @@ -use crate::raw_socket::{RawSocketHandle, RawSocketProtocol}; -use anyhow::{anyhow, Result}; -use etherparse::{ - IcmpEchoHeader, Icmpv4Header, Icmpv4Slice, Icmpv4Type, Icmpv6Header, Icmpv6Slice, Icmpv6Type, - IpNumber, NetSlice, SlicedPacket, -}; -use log::warn; -use std::{ - collections::HashMap, - net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, - os::fd::{FromRawFd, IntoRawFd}, - sync::Arc, - time::Duration, -}; -use tokio::{ - net::UdpSocket, - sync::{oneshot, Mutex}, - task::JoinHandle, - time::timeout, -}; - -#[derive(Debug)] -pub enum IcmpProtocol { - Icmpv4, - Icmpv6, -} - -impl IcmpProtocol { - pub fn to_socket_protocol(&self) -> RawSocketProtocol { - match self { - IcmpProtocol::Icmpv4 => RawSocketProtocol::Icmpv4, - IcmpProtocol::Icmpv6 => RawSocketProtocol::Icmpv6, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct IcmpHandlerToken(IpAddr, Option, u16); - -#[derive(Debug)] -pub enum IcmpReply { - Icmpv4 { - header: Icmpv4Header, - echo: IcmpEchoHeader, - payload: Vec, - }, - - Icmpv6 { - header: Icmpv6Header, - echo: IcmpEchoHeader, - payload: Vec, - }, -} - -type IcmpHandlerMap = Arc>>>; - -#[derive(Clone)] -pub struct IcmpClient { - socket: Arc, - handlers: IcmpHandlerMap, - task: Arc>>, -} - -impl IcmpClient { - pub fn new(protocol: IcmpProtocol) -> Result { - let handle = RawSocketHandle::new(protocol.to_socket_protocol())?; - let socket = unsafe { std::net::UdpSocket::from_raw_fd(handle.into_raw_fd()) }; - let socket: Arc = Arc::new(socket.try_into()?); - let handlers = Arc::new(Mutex::new(HashMap::new())); - let task = Arc::new(tokio::task::spawn(IcmpClient::process( - protocol, - socket.clone(), - handlers.clone(), - ))); - Ok(IcmpClient { - socket, - handlers, - task, - }) - } - - async fn process( - protocol: IcmpProtocol, - socket: Arc, - handlers: IcmpHandlerMap, - ) -> Result<()> { - let mut buffer = vec![0u8; 2048]; - loop { - let (size, addr) = socket.recv_from(&mut buffer).await?; - let packet = &buffer[0..size]; - - let (token, reply) = match protocol { - IcmpProtocol::Icmpv4 => { - let sliced = match SlicedPacket::from_ip(packet) { - Ok(sliced) => sliced, - Err(error) => { - warn!("received icmp packet but failed to parse it: {}", error); - continue; - } - }; - - let Some(NetSlice::Ipv4(ipv4)) = sliced.net else { - continue; - }; - - if ipv4.header().protocol() != IpNumber::ICMP { - continue; - } - - let Ok(icmpv4) = Icmpv4Slice::from_slice(ipv4.payload().payload) else { - continue; - }; - - let Icmpv4Type::EchoReply(echo) = icmpv4.header().icmp_type else { - continue; - }; - - let token = IcmpHandlerToken( - IpAddr::V4(ipv4.header().source_addr()), - Some(echo.id), - echo.seq, - ); - let reply = IcmpReply::Icmpv4 { - header: icmpv4.header(), - echo, - payload: icmpv4.payload().to_vec(), - }; - (token, reply) - } - - IcmpProtocol::Icmpv6 => { - let Ok(icmpv6) = Icmpv6Slice::from_slice(packet) else { - continue; - }; - - let Icmpv6Type::EchoReply(echo) = icmpv6.header().icmp_type else { - continue; - }; - - let SocketAddr::V6(addr) = addr else { - continue; - }; - - let token = IcmpHandlerToken(IpAddr::V6(*addr.ip()), Some(echo.id), echo.seq); - - let reply = IcmpReply::Icmpv6 { - header: icmpv6.header(), - echo, - payload: icmpv6.payload().to_vec(), - }; - (token, reply) - } - }; - - if let Some(sender) = handlers.lock().await.remove(&token) { - let _ = sender.send(reply); - } - } - } - - async fn add_handler(&self, token: IcmpHandlerToken) -> Result> { - let (tx, rx) = oneshot::channel(); - if self - .handlers - .lock() - .await - .insert(token.clone(), tx) - .is_some() - { - return Err(anyhow!("duplicate icmp request: {:?}", token)); - } - Ok(rx) - } - - async fn remove_handler(&self, token: IcmpHandlerToken) -> Result<()> { - self.handlers.lock().await.remove(&token); - Ok(()) - } - - pub async fn ping4( - &self, - addr: Ipv4Addr, - id: u16, - seq: u16, - payload: &[u8], - deadline: Duration, - ) -> Result> { - let token = IcmpHandlerToken(IpAddr::V4(addr), Some(id), seq); - let rx = self.add_handler(token.clone()).await?; - - let echo = IcmpEchoHeader { id, seq }; - let mut header = Icmpv4Header::new(Icmpv4Type::EchoRequest(echo)); - header.update_checksum(payload); - let mut buffer: Vec = Vec::new(); - header.write(&mut buffer)?; - buffer.extend_from_slice(payload); - - self.socket - .send_to(&buffer, SocketAddr::V4(SocketAddrV4::new(addr, 0))) - .await?; - - let result = timeout(deadline, rx).await; - self.remove_handler(token).await?; - let reply = match result { - Ok(Ok(packet)) => Some(packet), - Ok(Err(err)) => return Err(anyhow!("failed to wait for icmp packet: {}", err)), - Err(_) => None, - }; - Ok(reply) - } - - pub async fn ping6( - &self, - addr: Ipv6Addr, - id: u16, - seq: u16, - payload: &[u8], - deadline: Duration, - ) -> Result> { - let token = IcmpHandlerToken(IpAddr::V6(addr), Some(id), seq); - let rx = self.add_handler(token.clone()).await?; - - let echo = IcmpEchoHeader { id, seq }; - let header = Icmpv6Header::new(Icmpv6Type::EchoRequest(echo)); - let mut buffer: Vec = Vec::new(); - header.write(&mut buffer)?; - buffer.extend_from_slice(payload); - - self.socket - .send_to(&buffer, SocketAddr::V6(SocketAddrV6::new(addr, 0, 0, 0))) - .await?; - - let result = timeout(deadline, rx).await; - self.remove_handler(token).await?; - let reply = match result { - Ok(Ok(packet)) => Some(packet), - Ok(Err(err)) => return Err(anyhow!("failed to wait for icmp packet: {}", err)), - Err(_) => None, - }; - Ok(reply) - } -} - -impl Drop for IcmpClient { - fn drop(&mut self) { - if Arc::strong_count(&self.task) <= 1 { - self.task.abort(); - } - } -} diff --git a/crates/network/src/lib.rs b/crates/network/src/lib.rs deleted file mode 100644 index be933094..00000000 --- a/crates/network/src/lib.rs +++ /dev/null @@ -1,150 +0,0 @@ -use std::{collections::HashMap, str::FromStr, time::Duration}; - -use anyhow::{anyhow, Result}; -use autonet::{AutoNetworkChangeset, AutoNetworkWatcher, NetworkMetadata}; -use futures::{future::join_all, TryFutureExt}; -use hbridge::HostBridge; -use krata::{ - client::ControlClientProvider, - dial::ControlDialAddress, - v1::{ - common::Zone, - control::{control_service_client::ControlServiceClient, GetHostStatusRequest}, - }, -}; -use log::warn; -use smoltcp::wire::{EthernetAddress, Ipv4Cidr, Ipv6Cidr}; -use tokio::{task::JoinHandle, time::sleep}; -use tonic::{transport::Channel, Request}; -use uuid::Uuid; -use vbridge::VirtualBridge; - -use crate::backend::NetworkBackend; - -pub mod autonet; -pub mod backend; -pub mod chandev; -pub mod hbridge; -pub mod icmp; -pub mod nat; -pub mod pkt; -pub mod proxynat; -pub mod raw_socket; -pub mod vbridge; - -const HOST_BRIDGE_MTU: usize = 1500; -pub const EXTRA_MTU: usize = 20; - -pub struct NetworkService { - pub control: ControlServiceClient, - pub zones: HashMap, - pub backends: HashMap>, - pub bridge: VirtualBridge, - pub hbridge: HostBridge, -} - -impl NetworkService { - pub async fn new(control_address: ControlDialAddress) -> Result { - let mut control = ControlClientProvider::dial(control_address).await?; - let host_status = control - .get_host_status(Request::new(GetHostStatusRequest {})) - .await? - .into_inner(); - let host_ipv4 = Ipv4Cidr::from_str(&host_status.host_ipv4) - .map_err(|_| anyhow!("failed to parse host ipv4 cidr"))?; - let host_ipv6 = Ipv6Cidr::from_str(&host_status.host_ipv6) - .map_err(|_| anyhow!("failed to parse host ipv6 cidr"))?; - let host_mac = EthernetAddress::from_str(&host_status.host_mac) - .map_err(|_| anyhow!("failed to parse host mac address"))?; - let bridge = VirtualBridge::new()?; - let hbridge = HostBridge::new( - HOST_BRIDGE_MTU + EXTRA_MTU, - "krata0".to_string(), - &bridge, - host_ipv4, - host_ipv6, - host_mac, - ) - .await?; - Ok(NetworkService { - control, - zones: HashMap::new(), - backends: HashMap::new(), - bridge, - hbridge, - }) - } -} - -impl NetworkService { - pub async fn watch(&mut self) -> Result<()> { - let mut watcher = AutoNetworkWatcher::new(self.control.clone()).await?; - let mut receiver = watcher.events.subscribe(); - loop { - let changeset = watcher.read_changes().await?; - self.process_network_changeset(&mut watcher, changeset) - .await?; - watcher.wait(&mut receiver).await?; - } - } - - async fn process_network_changeset( - &mut self, - collector: &mut AutoNetworkWatcher, - changeset: AutoNetworkChangeset, - ) -> Result<()> { - for removal in &changeset.removed { - if let Some(handle) = self.backends.remove(&removal.uuid) { - handle.abort(); - } - } - - let futures = changeset - .added - .iter() - .map(|metadata| { - self.add_network_backend(metadata) - .map_err(|x| (metadata.clone(), x)) - }) - .collect::>(); - - sleep(Duration::from_secs(1)).await; - let mut failed: Vec = Vec::new(); - let mut launched: Vec<(Uuid, JoinHandle<()>)> = Vec::new(); - let results = join_all(futures).await; - for result in results { - match result { - Ok(launch) => { - launched.push(launch); - } - - Err((metadata, error)) => { - warn!( - "failed to launch network backend for krata zone {}: {}", - metadata.uuid, error - ); - failed.push(metadata.uuid); - } - }; - } - - for (uuid, handle) in launched { - self.backends.insert(uuid, handle); - } - - for uuid in failed { - collector.mark_unknown(uuid)?; - } - - Ok(()) - } - - async fn add_network_backend( - &self, - metadata: &NetworkMetadata, - ) -> Result<(Uuid, JoinHandle<()>)> { - let mut network = NetworkBackend::new(metadata.clone(), self.bridge.clone())?; - network.init().await?; - Ok((metadata.uuid, network.launch().await?)) - } -} diff --git a/crates/network/src/nat/handler.rs b/crates/network/src/nat/handler.rs deleted file mode 100644 index 1ce300ba..00000000 --- a/crates/network/src/nat/handler.rs +++ /dev/null @@ -1,36 +0,0 @@ -use anyhow::Result; -use async_trait::async_trait; -use bytes::BytesMut; -use tokio::sync::mpsc::Sender; - -use super::key::NatKey; - -#[derive(Debug, Clone)] -pub struct NatHandlerContext { - pub mtu: usize, - pub key: NatKey, - pub transmit_sender: Sender, - pub reclaim_sender: Sender, -} - -impl NatHandlerContext { - pub fn try_transmit(&self, buffer: BytesMut) -> Result<()> { - self.transmit_sender.try_send(buffer)?; - Ok(()) - } - - pub async fn reclaim(&self) -> Result<()> { - let _ = self.reclaim_sender.try_send(self.key); - Ok(()) - } -} - -#[async_trait] -pub trait NatHandler: Send { - async fn receive(&self, packet: &[u8]) -> Result; -} - -#[async_trait] -pub trait NatHandlerFactory: Send { - async fn nat(&self, context: NatHandlerContext) -> Option>; -} diff --git a/crates/network/src/nat/key.rs b/crates/network/src/nat/key.rs deleted file mode 100644 index ac1447dc..00000000 --- a/crates/network/src/nat/key.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::fmt::Display; - -use smoltcp::wire::{EthernetAddress, IpEndpoint}; - -#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub enum NatKeyProtocol { - Tcp, - Udp, - Icmp, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub struct NatKey { - pub protocol: NatKeyProtocol, - pub client_mac: EthernetAddress, - pub local_mac: EthernetAddress, - pub client_ip: IpEndpoint, - pub external_ip: IpEndpoint, -} - -impl Display for NatKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{} -> {} {:?} {} -> {}", - self.client_mac, self.local_mac, self.protocol, self.client_ip, self.external_ip - ) - } -} diff --git a/crates/network/src/nat/mod.rs b/crates/network/src/nat/mod.rs deleted file mode 100644 index ca0a404f..00000000 --- a/crates/network/src/nat/mod.rs +++ /dev/null @@ -1,42 +0,0 @@ -use anyhow::Result; -use tokio::sync::mpsc::Sender; - -use self::handler::NatHandlerFactory; -use self::processor::NatProcessor; -use bytes::BytesMut; -use smoltcp::wire::EthernetAddress; -use smoltcp::wire::IpCidr; -use tokio::task::JoinHandle; - -pub mod handler; -pub mod key; -pub mod processor; -pub mod table; - -pub struct Nat { - pub receive_sender: Sender, - task: JoinHandle<()>, -} - -impl Nat { - pub fn new( - mtu: usize, - factory: Box, - local_mac: EthernetAddress, - local_cidrs: Vec, - transmit_sender: Sender, - ) -> Result { - let (receive_sender, task) = - NatProcessor::launch(mtu, factory, local_mac, local_cidrs, transmit_sender)?; - Ok(Self { - receive_sender, - task, - }) - } -} - -impl Drop for Nat { - fn drop(&mut self) { - self.task.abort(); - } -} diff --git a/crates/network/src/nat/processor.rs b/crates/network/src/nat/processor.rs deleted file mode 100644 index 519fa1c9..00000000 --- a/crates/network/src/nat/processor.rs +++ /dev/null @@ -1,330 +0,0 @@ -use crate::pkt::RecvPacket; -use crate::pkt::RecvPacketIp; -use anyhow::Result; -use bytes::BytesMut; -use etherparse::Icmpv4Header; -use etherparse::Icmpv4Type; -use etherparse::Icmpv6Header; -use etherparse::Icmpv6Type; -use etherparse::IpNumber; -use etherparse::IpPayloadSlice; -use etherparse::Ipv4Slice; -use etherparse::Ipv6Slice; -use etherparse::SlicedPacket; -use etherparse::TcpHeaderSlice; -use etherparse::UdpHeaderSlice; -use log::warn; -use log::{debug, trace}; -use smoltcp::wire::EthernetAddress; -use smoltcp::wire::IpAddress; -use smoltcp::wire::IpCidr; -use smoltcp::wire::IpEndpoint; -use std::collections::hash_map::Entry; -use tokio::select; -use tokio::sync::mpsc::channel; -use tokio::sync::mpsc::Receiver; -use tokio::sync::mpsc::Sender; -use tokio::task::JoinHandle; - -use super::handler::NatHandler; -use super::handler::NatHandlerContext; -use super::handler::NatHandlerFactory; -use super::key::NatKey; -use super::key::NatKeyProtocol; -use super::table::NatTable; - -const RECEIVE_CHANNEL_QUEUE_LEN: usize = 3000; -const RECLAIM_CHANNEL_QUEUE_LEN: usize = 30; - -pub struct NatProcessor { - mtu: usize, - local_mac: EthernetAddress, - local_cidrs: Vec, - table: NatTable, - factory: Box, - transmit_sender: Sender, - reclaim_sender: Sender, - reclaim_receiver: Receiver, - receive_receiver: Receiver, -} - -enum NatProcessorSelect { - Reclaim(Option), - ReceivedPacket(Option), -} - -impl NatProcessor { - pub fn launch( - mtu: usize, - factory: Box, - local_mac: EthernetAddress, - local_cidrs: Vec, - transmit_sender: Sender, - ) -> Result<(Sender, JoinHandle<()>)> { - let (reclaim_sender, reclaim_receiver) = channel(RECLAIM_CHANNEL_QUEUE_LEN); - let (receive_sender, receive_receiver) = channel(RECEIVE_CHANNEL_QUEUE_LEN); - let mut processor = Self { - mtu, - local_mac, - local_cidrs, - factory, - table: NatTable::new(), - transmit_sender, - reclaim_sender, - receive_receiver, - reclaim_receiver, - }; - - let handle = tokio::task::spawn(async move { - if let Err(error) = processor.process().await { - warn!("nat processing failed: {}", error); - } - }); - - Ok((receive_sender, handle)) - } - - pub async fn process(&mut self) -> Result<()> { - loop { - let selection = select! { - x = self.reclaim_receiver.recv() => NatProcessorSelect::Reclaim(x), - x = self.receive_receiver.recv() => NatProcessorSelect::ReceivedPacket(x), - }; - - match selection { - NatProcessorSelect::Reclaim(Some(key)) => { - if self.table.inner.remove(&key).is_some() { - debug!("reclaimed nat key: {}", key); - } - } - - NatProcessorSelect::ReceivedPacket(Some(packet)) => { - if let Ok(slice) = SlicedPacket::from_ethernet(&packet) { - let Ok(packet) = RecvPacket::new(&packet, &slice) else { - continue; - }; - - self.process_packet(&packet).await?; - } - } - - NatProcessorSelect::ReceivedPacket(None) | NatProcessorSelect::Reclaim(None) => { - break - } - } - } - Ok(()) - } - - pub async fn process_reclaim(&mut self) -> Result> { - Ok(if let Some(key) = self.reclaim_receiver.recv().await { - if self.table.inner.remove(&key).is_some() { - debug!("reclaimed nat key: {}", key); - Some(key) - } else { - None - } - } else { - None - }) - } - - pub async fn process_packet<'a>(&mut self, packet: &RecvPacket<'a>) -> Result<()> { - let Some(ether) = packet.ether else { - return Ok(()); - }; - - let mac = EthernetAddress(ether.destination()); - if mac != self.local_mac { - trace!( - "received packet with destination {} which is not the local mac {}", - mac, - self.local_mac - ); - return Ok(()); - } - - let key = match packet.ip { - Some(RecvPacketIp::Ipv4(ipv4)) => self.extract_key_ipv4(packet, ipv4)?, - Some(RecvPacketIp::Ipv6(ipv6)) => self.extract_key_ipv6(packet, ipv6)?, - _ => None, - }; - - let Some(key) = key else { - return Ok(()); - }; - - for cidr in &self.local_cidrs { - if cidr.contains_addr(&key.external_ip.addr) { - return Ok(()); - } - } - - let context = NatHandlerContext { - mtu: self.mtu, - key, - transmit_sender: self.transmit_sender.clone(), - reclaim_sender: self.reclaim_sender.clone(), - }; - let handler: Option<&mut Box> = match self.table.inner.entry(key) { - Entry::Occupied(entry) => Some(entry.into_mut()), - Entry::Vacant(entry) => { - if let Some(handler) = self.factory.nat(context).await { - debug!("creating nat entry for key: {}", key); - Some(entry.insert(handler)) - } else { - None - } - } - }; - - if let Some(handler) = handler { - if !handler.receive(packet.raw).await? { - self.reclaim_sender.try_send(key)?; - } - } - Ok(()) - } - - pub fn extract_key_ipv4<'a>( - &mut self, - packet: &RecvPacket<'a>, - ipv4: &Ipv4Slice<'a>, - ) -> Result> { - let source_addr = IpAddress::Ipv4(ipv4.header().source_addr().into()); - let dest_addr = IpAddress::Ipv4(ipv4.header().destination_addr().into()); - Ok(match ipv4.header().protocol() { - IpNumber::TCP => { - self.extract_key_tcp(packet, source_addr, dest_addr, ipv4.payload())? - } - - IpNumber::UDP => { - self.extract_key_udp(packet, source_addr, dest_addr, ipv4.payload())? - } - - IpNumber::ICMP => { - self.extract_key_icmpv4(packet, source_addr, dest_addr, ipv4.payload())? - } - - _ => None, - }) - } - - pub fn extract_key_ipv6<'a>( - &mut self, - packet: &RecvPacket<'a>, - ipv6: &Ipv6Slice<'a>, - ) -> Result> { - let source_addr = IpAddress::Ipv6(ipv6.header().source_addr().into()); - let dest_addr = IpAddress::Ipv6(ipv6.header().destination_addr().into()); - Ok(match ipv6.header().next_header() { - IpNumber::TCP => { - self.extract_key_tcp(packet, source_addr, dest_addr, ipv6.payload())? - } - - IpNumber::UDP => { - self.extract_key_udp(packet, source_addr, dest_addr, ipv6.payload())? - } - - IpNumber::IPV6_ICMP => { - self.extract_key_icmpv6(packet, source_addr, dest_addr, ipv6.payload())? - } - - _ => None, - }) - } - - pub fn extract_key_udp<'a>( - &mut self, - packet: &RecvPacket<'a>, - source_addr: IpAddress, - dest_addr: IpAddress, - payload: &IpPayloadSlice<'a>, - ) -> Result> { - let Some(ether) = packet.ether else { - return Ok(None); - }; - let header = UdpHeaderSlice::from_slice(payload.payload)?; - let source = IpEndpoint::new(source_addr, header.source_port()); - let dest = IpEndpoint::new(dest_addr, header.destination_port()); - Ok(Some(NatKey { - protocol: NatKeyProtocol::Udp, - client_mac: EthernetAddress(ether.source()), - local_mac: EthernetAddress(ether.destination()), - client_ip: source, - external_ip: dest, - })) - } - - pub fn extract_key_icmpv4<'a>( - &mut self, - packet: &RecvPacket<'a>, - source_addr: IpAddress, - dest_addr: IpAddress, - payload: &IpPayloadSlice<'a>, - ) -> Result> { - let Some(ether) = packet.ether else { - return Ok(None); - }; - let (header, _) = Icmpv4Header::from_slice(payload.payload)?; - let Icmpv4Type::EchoRequest(_) = header.icmp_type else { - return Ok(None); - }; - let source = IpEndpoint::new(source_addr, 0); - let dest = IpEndpoint::new(dest_addr, 0); - Ok(Some(NatKey { - protocol: NatKeyProtocol::Icmp, - client_mac: EthernetAddress(ether.source()), - local_mac: EthernetAddress(ether.destination()), - client_ip: source, - external_ip: dest, - })) - } - - pub fn extract_key_icmpv6<'a>( - &mut self, - packet: &RecvPacket<'a>, - source_addr: IpAddress, - dest_addr: IpAddress, - payload: &IpPayloadSlice<'a>, - ) -> Result> { - let Some(ether) = packet.ether else { - return Ok(None); - }; - let (header, _) = Icmpv6Header::from_slice(payload.payload)?; - let Icmpv6Type::EchoRequest(_) = header.icmp_type else { - return Ok(None); - }; - let source = IpEndpoint::new(source_addr, 0); - let dest = IpEndpoint::new(dest_addr, 0); - Ok(Some(NatKey { - protocol: NatKeyProtocol::Icmp, - client_mac: EthernetAddress(ether.source()), - local_mac: EthernetAddress(ether.destination()), - client_ip: source, - external_ip: dest, - })) - } - - pub fn extract_key_tcp<'a>( - &mut self, - packet: &RecvPacket<'a>, - source_addr: IpAddress, - dest_addr: IpAddress, - payload: &IpPayloadSlice<'a>, - ) -> Result> { - let Some(ether) = packet.ether else { - return Ok(None); - }; - let header = TcpHeaderSlice::from_slice(payload.payload)?; - let source = IpEndpoint::new(source_addr, header.source_port()); - let dest = IpEndpoint::new(dest_addr, header.destination_port()); - Ok(Some(NatKey { - protocol: NatKeyProtocol::Tcp, - client_mac: EthernetAddress(ether.source()), - local_mac: EthernetAddress(ether.destination()), - client_ip: source, - external_ip: dest, - })) - } -} diff --git a/crates/network/src/nat/table.rs b/crates/network/src/nat/table.rs deleted file mode 100644 index 79e4a5ac..00000000 --- a/crates/network/src/nat/table.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::collections::HashMap; - -use super::{handler::NatHandler, key::NatKey}; - -pub struct NatTable { - pub inner: HashMap>, -} - -impl Default for NatTable { - fn default() -> Self { - Self::new() - } -} - -impl NatTable { - pub fn new() -> Self { - Self { - inner: HashMap::new(), - } - } -} diff --git a/crates/network/src/pkt.rs b/crates/network/src/pkt.rs deleted file mode 100644 index 91dbfed9..00000000 --- a/crates/network/src/pkt.rs +++ /dev/null @@ -1,37 +0,0 @@ -use anyhow::Result; -use etherparse::{Ethernet2Slice, Ipv4Slice, Ipv6Slice, LinkSlice, NetSlice, SlicedPacket}; - -pub enum RecvPacketIp<'a> { - Ipv4(&'a Ipv4Slice<'a>), - Ipv6(&'a Ipv6Slice<'a>), -} - -pub struct RecvPacket<'a> { - pub raw: &'a [u8], - pub slice: &'a SlicedPacket<'a>, - pub ether: Option<&'a Ethernet2Slice<'a>>, - pub ip: Option>, -} - -impl RecvPacket<'_> { - pub fn new<'a>(raw: &'a [u8], slice: &'a SlicedPacket<'a>) -> Result> { - let ether = match slice.link { - Some(LinkSlice::Ethernet2(ref ether)) => Some(ether), - _ => None, - }; - - let ip = match slice.net { - Some(NetSlice::Ipv4(ref ipv4)) => Some(RecvPacketIp::Ipv4(ipv4)), - Some(NetSlice::Ipv6(ref ipv6)) => Some(RecvPacketIp::Ipv6(ipv6)), - _ => None, - }; - - let packet = RecvPacket { - raw, - slice, - ether, - ip, - }; - Ok(packet) - } -} diff --git a/crates/network/src/proxynat/icmp.rs b/crates/network/src/proxynat/icmp.rs deleted file mode 100644 index e73653f5..00000000 --- a/crates/network/src/proxynat/icmp.rs +++ /dev/null @@ -1,276 +0,0 @@ -use std::{ - net::{IpAddr, Ipv4Addr, Ipv6Addr}, - time::Duration, -}; - -use anyhow::{anyhow, Result}; -use async_trait::async_trait; -use bytes::{BufMut, BytesMut}; -use etherparse::{ - IcmpEchoHeader, Icmpv4Header, Icmpv4Type, Icmpv6Header, Icmpv6Type, IpNumber, Ipv4Slice, - Ipv6Slice, NetSlice, PacketBuilder, SlicedPacket, -}; -use log::{debug, trace, warn}; -use smoltcp::wire::IpAddress; -use tokio::{ - select, - sync::mpsc::{Receiver, Sender}, -}; - -use crate::{ - icmp::{IcmpClient, IcmpProtocol, IcmpReply}, - nat::handler::{NatHandler, NatHandlerContext}, -}; - -const ICMP_PING_TIMEOUT_SECS: u64 = 20; -const ICMP_TIMEOUT_SECS: u64 = 30; - -pub struct ProxyIcmpHandler { - rx_sender: Sender, -} - -#[async_trait] -impl NatHandler for ProxyIcmpHandler { - async fn receive(&self, data: &[u8]) -> Result { - if self.rx_sender.is_closed() { - Ok(true) - } else { - self.rx_sender.try_send(data.into())?; - Ok(true) - } - } -} - -enum ProxyIcmpSelect { - Internal(BytesMut), - Close, -} - -impl ProxyIcmpHandler { - pub fn new(rx_sender: Sender) -> Self { - ProxyIcmpHandler { rx_sender } - } - - pub async fn spawn( - &mut self, - context: NatHandlerContext, - rx_receiver: Receiver, - ) -> Result<()> { - let client = IcmpClient::new(match context.key.external_ip.addr { - IpAddress::Ipv4(_) => IcmpProtocol::Icmpv4, - IpAddress::Ipv6(_) => IcmpProtocol::Icmpv6, - })?; - tokio::spawn(async move { - if let Err(error) = ProxyIcmpHandler::process(client, rx_receiver, context).await { - warn!("processing of icmp proxy failed: {}", error); - } - }); - Ok(()) - } - - async fn process( - client: IcmpClient, - mut rx_receiver: Receiver, - context: NatHandlerContext, - ) -> Result<()> { - loop { - let deadline = tokio::time::sleep(Duration::from_secs(ICMP_TIMEOUT_SECS)); - let selection = select! { - x = rx_receiver.recv() => if let Some(data) = x { - ProxyIcmpSelect::Internal(data) - } else { - ProxyIcmpSelect::Close - }, - _ = deadline => ProxyIcmpSelect::Close, - }; - - match selection { - ProxyIcmpSelect::Internal(data) => { - let packet = SlicedPacket::from_ethernet(&data)?; - let Some(ref net) = packet.net else { - continue; - }; - - match net { - NetSlice::Ipv4(ipv4) => { - ProxyIcmpHandler::process_ipv4(&context, ipv4, &client).await? - } - - NetSlice::Ipv6(ipv6) => { - ProxyIcmpHandler::process_ipv6(&context, ipv6, &client).await? - } - } - } - - ProxyIcmpSelect::Close => { - break; - } - } - } - - context.reclaim().await?; - - Ok(()) - } - - async fn process_ipv4( - context: &NatHandlerContext, - ipv4: &Ipv4Slice<'_>, - client: &IcmpClient, - ) -> Result<()> { - if ipv4.header().protocol() != IpNumber::ICMP { - return Ok(()); - } - - let (header, payload) = Icmpv4Header::from_slice(ipv4.payload().payload)?; - if let Icmpv4Type::EchoRequest(echo) = header.icmp_type { - let IpAddr::V4(external_ipv4) = context.key.external_ip.addr.into() else { - return Ok(()); - }; - - let context = context.clone(); - let client = client.clone(); - let payload = payload.to_vec(); - tokio::task::spawn(async move { - if let Err(error) = ProxyIcmpHandler::process_echo_ipv4( - context, - client, - external_ipv4, - echo, - payload, - ) - .await - { - trace!("icmp4 echo failed: {}", error); - } - }); - } - Ok(()) - } - - async fn process_ipv6( - context: &NatHandlerContext, - ipv6: &Ipv6Slice<'_>, - client: &IcmpClient, - ) -> Result<()> { - if ipv6.header().next_header() != IpNumber::IPV6_ICMP { - return Ok(()); - } - - let (header, payload) = Icmpv6Header::from_slice(ipv6.payload().payload)?; - if let Icmpv6Type::EchoRequest(echo) = header.icmp_type { - let IpAddr::V6(external_ipv6) = context.key.external_ip.addr.into() else { - return Ok(()); - }; - - let context = context.clone(); - let client = client.clone(); - let payload = payload.to_vec(); - tokio::task::spawn(async move { - if let Err(error) = ProxyIcmpHandler::process_echo_ipv6( - context, - client, - external_ipv6, - echo, - payload, - ) - .await - { - trace!("icmp6 echo failed: {}", error); - } - }); - } - - Ok(()) - } - - async fn process_echo_ipv4( - context: NatHandlerContext, - client: IcmpClient, - external_ipv4: Ipv4Addr, - echo: IcmpEchoHeader, - payload: Vec, - ) -> Result<()> { - let reply = client - .ping4( - external_ipv4, - echo.id, - echo.seq, - &payload, - Duration::from_secs(ICMP_PING_TIMEOUT_SECS), - ) - .await?; - let Some(IcmpReply::Icmpv4 { - header: _, - echo, - payload, - }) = reply - else { - return Ok(()); - }; - - let packet = PacketBuilder::ethernet2(context.key.local_mac.0, context.key.client_mac.0); - let packet = match (context.key.external_ip.addr, context.key.client_ip.addr) { - (IpAddress::Ipv4(external_addr), IpAddress::Ipv4(client_addr)) => { - packet.ipv4(external_addr.0, client_addr.0, 20) - } - _ => { - return Err(anyhow!("IP endpoint mismatch")); - } - }; - let packet = packet.icmpv4_echo_reply(echo.id, echo.seq); - let buffer = BytesMut::with_capacity(packet.size(payload.len())); - let mut writer = buffer.writer(); - packet.write(&mut writer, &payload)?; - let buffer = writer.into_inner(); - if let Err(error) = context.try_transmit(buffer) { - debug!("failed to transmit icmp packet: {}", error); - } - Ok(()) - } - - async fn process_echo_ipv6( - context: NatHandlerContext, - client: IcmpClient, - external_ipv6: Ipv6Addr, - echo: IcmpEchoHeader, - payload: Vec, - ) -> Result<()> { - let reply = client - .ping6( - external_ipv6, - echo.id, - echo.seq, - &payload, - Duration::from_secs(ICMP_PING_TIMEOUT_SECS), - ) - .await?; - let Some(IcmpReply::Icmpv6 { - header: _, - echo, - payload, - }) = reply - else { - return Ok(()); - }; - - let packet = PacketBuilder::ethernet2(context.key.local_mac.0, context.key.client_mac.0); - let packet = match (context.key.external_ip.addr, context.key.client_ip.addr) { - (IpAddress::Ipv6(external_addr), IpAddress::Ipv6(client_addr)) => { - packet.ipv6(external_addr.0, client_addr.0, 20) - } - _ => { - return Err(anyhow!("IP endpoint mismatch")); - } - }; - let packet = packet.icmpv6_echo_reply(echo.id, echo.seq); - let buffer = BytesMut::with_capacity(packet.size(payload.len())); - let mut writer = buffer.writer(); - packet.write(&mut writer, &payload)?; - let buffer = writer.into_inner(); - if let Err(error) = context.try_transmit(buffer) { - debug!("failed to transmit icmp packet: {}", error); - } - Ok(()) - } -} diff --git a/crates/network/src/proxynat/mod.rs b/crates/network/src/proxynat/mod.rs deleted file mode 100644 index d69d229e..00000000 --- a/crates/network/src/proxynat/mod.rs +++ /dev/null @@ -1,77 +0,0 @@ -use async_trait::async_trait; - -use bytes::BytesMut; -use log::warn; - -use tokio::sync::mpsc::channel; - -use crate::proxynat::udp::ProxyUdpHandler; - -use crate::nat::handler::{NatHandler, NatHandlerContext, NatHandlerFactory}; -use crate::nat::key::NatKeyProtocol; - -use self::icmp::ProxyIcmpHandler; -use self::tcp::ProxyTcpHandler; - -mod icmp; -mod tcp; -mod udp; - -const RX_CHANNEL_QUEUE_LEN: usize = 1000; - -pub struct ProxyNatHandlerFactory {} - -impl Default for ProxyNatHandlerFactory { - fn default() -> Self { - Self::new() - } -} - -impl ProxyNatHandlerFactory { - pub fn new() -> Self { - Self {} - } -} - -#[async_trait] -impl NatHandlerFactory for ProxyNatHandlerFactory { - async fn nat(&self, context: NatHandlerContext) -> Option> { - match context.key.protocol { - NatKeyProtocol::Udp => { - let (rx_sender, rx_receiver) = channel::(RX_CHANNEL_QUEUE_LEN); - let mut handler = ProxyUdpHandler::new(rx_sender); - - if let Err(error) = handler.spawn(context, rx_receiver).await { - warn!("unable to spawn udp proxy handler: {}", error); - None - } else { - Some(Box::new(handler)) - } - } - - NatKeyProtocol::Icmp => { - let (rx_sender, rx_receiver) = channel::(RX_CHANNEL_QUEUE_LEN); - let mut handler = ProxyIcmpHandler::new(rx_sender); - - if let Err(error) = handler.spawn(context, rx_receiver).await { - warn!("unable to spawn icmp proxy handler: {}", error); - None - } else { - Some(Box::new(handler)) - } - } - - NatKeyProtocol::Tcp => { - let (rx_sender, rx_receiver) = channel::(RX_CHANNEL_QUEUE_LEN); - let mut handler = ProxyTcpHandler::new(rx_sender); - - if let Err(error) = handler.spawn(context, rx_receiver).await { - warn!("unable to spawn tcp proxy handler: {}", error); - None - } else { - Some(Box::new(handler)) - } - } - } - } -} diff --git a/crates/network/src/proxynat/tcp.rs b/crates/network/src/proxynat/tcp.rs deleted file mode 100644 index c348bf31..00000000 --- a/crates/network/src/proxynat/tcp.rs +++ /dev/null @@ -1,466 +0,0 @@ -use std::{ - net::{IpAddr, SocketAddr}, - time::Duration, -}; - -use anyhow::Result; -use async_trait::async_trait; -use bytes::BytesMut; -use etherparse::{EtherType, Ethernet2Header}; -use log::{debug, warn}; -use smoltcp::{ - iface::{Config, Interface, SocketSet, SocketStorage}, - phy::Medium, - socket::tcp::{self, SocketBuffer, State}, - time::Instant, - wire::{HardwareAddress, IpAddress, IpCidr}, -}; -use tokio::{ - io::{AsyncReadExt, AsyncWriteExt}, - net::TcpStream, - select, - sync::mpsc::channel, -}; -use tokio::{sync::mpsc::Receiver, sync::mpsc::Sender}; - -use crate::{ - chandev::ChannelDevice, - nat::handler::{NatHandler, NatHandlerContext}, -}; - -const TCP_BUFFER_SIZE: usize = 65535; -const TCP_IP_BUFFER_QUEUE_LEN: usize = 3000; -const TCP_ACCEPT_TIMEOUT_SECS: u64 = 120; -const TCP_DANGLE_TIMEOUT_SECS: u64 = 10; - -pub struct ProxyTcpHandler { - rx_sender: Sender, -} - -#[async_trait] -impl NatHandler for ProxyTcpHandler { - async fn receive(&self, data: &[u8]) -> Result { - if self.rx_sender.is_closed() { - Ok(false) - } else { - self.rx_sender.try_send(data.into())?; - Ok(true) - } - } -} - -#[derive(Debug)] -enum ProxyTcpAcceptSelect { - Internal(BytesMut), - TxIpPacket(BytesMut), - TimePassed, - DoNothing, - Close, -} - -#[derive(Debug)] -enum ProxyTcpDataSelect { - ExternalRecv(usize), - ExternalSent(usize), - InternalRecv(BytesMut), - TxIpPacket(BytesMut), - TimePassed, - DoNothing, - Close, -} - -#[derive(Debug)] -enum ProxyTcpFinishSelect { - InternalRecv(BytesMut), - TxIpPacket(BytesMut), - Close, -} - -impl ProxyTcpHandler { - pub fn new(rx_sender: Sender) -> Self { - ProxyTcpHandler { rx_sender } - } - - pub async fn spawn( - &mut self, - context: NatHandlerContext, - rx_receiver: Receiver, - ) -> Result<()> { - let external_addr = match context.key.external_ip.addr { - IpAddress::Ipv4(addr) => { - SocketAddr::new(IpAddr::V4(addr.0.into()), context.key.external_ip.port) - } - IpAddress::Ipv6(addr) => { - SocketAddr::new(IpAddr::V6(addr.0.into()), context.key.external_ip.port) - } - }; - - let socket = TcpStream::connect(external_addr).await?; - tokio::spawn(async move { - if let Err(error) = ProxyTcpHandler::process(context, socket, rx_receiver).await { - warn!("processing of tcp proxy failed: {}", error); - } - }); - Ok(()) - } - - async fn process( - context: NatHandlerContext, - mut external_socket: TcpStream, - mut rx_receiver: Receiver, - ) -> Result<()> { - let (ip_sender, mut ip_receiver) = channel::(TCP_IP_BUFFER_QUEUE_LEN); - let mut external_buffer = vec![0u8; TCP_BUFFER_SIZE]; - - let mut device = ChannelDevice::new( - context.mtu - Ethernet2Header::LEN, - Medium::Ip, - ip_sender.clone(), - ); - let config = Config::new(HardwareAddress::Ip); - - let tcp_rx_buffer = SocketBuffer::new(vec![0; TCP_BUFFER_SIZE]); - let tcp_tx_buffer = SocketBuffer::new(vec![0; TCP_BUFFER_SIZE]); - let internal_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); - let mut iface = Interface::new(config, &mut device, Instant::now()); - - iface.update_ip_addrs(|addrs| { - let _ = addrs.push(IpCidr::new(context.key.external_ip.addr, 0)); - }); - - let mut sockets = SocketSet::new([SocketStorage::EMPTY]); - let internal_socket_handle = sockets.add(internal_socket); - let (mut external_r, mut external_w) = external_socket.split(); - - { - let socket = sockets.get_mut::(internal_socket_handle); - socket.connect( - iface.context(), - context.key.client_ip, - context.key.external_ip, - )?; - } - - iface.poll(Instant::now(), &mut device, &mut sockets); - - let mut sleeper: Option = None; - loop { - let socket = sockets.get_mut::(internal_socket_handle); - if socket.is_active() && socket.state() != State::SynSent { - break; - } - - if socket.state() == State::Closed { - break; - } - - let deadline = tokio::time::sleep(Duration::from_secs(TCP_ACCEPT_TIMEOUT_SECS)); - let selection = if let Some(sleep) = sleeper.take() { - select! { - biased; - x = rx_receiver.recv() => if let Some(data) = x { - ProxyTcpAcceptSelect::Internal(data) - } else { - ProxyTcpAcceptSelect::Close - }, - x = ip_receiver.recv() => if let Some(data) = x { - ProxyTcpAcceptSelect::TxIpPacket(data) - } else { - ProxyTcpAcceptSelect::Close - }, - _ = sleep => ProxyTcpAcceptSelect::TimePassed, - _ = deadline => ProxyTcpAcceptSelect::Close, - } - } else { - select! { - biased; - x = rx_receiver.recv() => if let Some(data) = x { - ProxyTcpAcceptSelect::Internal(data) - } else { - ProxyTcpAcceptSelect::Close - }, - x = ip_receiver.recv() => if let Some(data) = x { - ProxyTcpAcceptSelect::TxIpPacket(data) - } else { - ProxyTcpAcceptSelect::Close - }, - _ = std::future::ready(()) => ProxyTcpAcceptSelect::DoNothing, - _ = deadline => ProxyTcpAcceptSelect::Close, - } - }; - match selection { - ProxyTcpAcceptSelect::TimePassed => { - iface.poll(Instant::now(), &mut device, &mut sockets); - } - - ProxyTcpAcceptSelect::DoNothing => { - sleeper = Some(tokio::time::sleep(Duration::from_micros(100))); - } - - ProxyTcpAcceptSelect::Internal(data) => { - let (_, payload) = Ethernet2Header::from_slice(&data)?; - device.rx = Some(payload.into()); - iface.poll(Instant::now(), &mut device, &mut sockets); - } - - ProxyTcpAcceptSelect::TxIpPacket(payload) => { - let mut buffer = BytesMut::with_capacity(Ethernet2Header::LEN + payload.len()); - let header = Ethernet2Header { - source: context.key.local_mac.0, - destination: context.key.client_mac.0, - ether_type: match context.key.external_ip.addr { - IpAddress::Ipv4(_) => EtherType::IPV4, - IpAddress::Ipv6(_) => EtherType::IPV6, - }, - }; - buffer.extend_from_slice(&header.to_bytes()); - buffer.extend_from_slice(&payload); - if let Err(error) = context.try_transmit(buffer) { - debug!("failed to transmit tcp packet: {}", error); - } - } - - ProxyTcpAcceptSelect::Close => { - break; - } - } - } - - let accepted = if sockets - .get_mut::(internal_socket_handle) - .is_active() - { - true - } else { - debug!("failed to accept tcp connection from client"); - false - }; - - let mut already_shutdown = false; - let mut sleeper: Option = None; - loop { - if !accepted { - break; - } - - let socket = sockets.get_mut::(internal_socket_handle); - - match socket.state() { - State::Closed - | State::Listen - | State::Closing - | State::LastAck - | State::TimeWait => { - break; - } - State::FinWait1 - | State::SynSent - | State::CloseWait - | State::FinWait2 - | State::SynReceived - | State::Established => {} - } - - let bytes_to_client = if socket.can_send() { - socket.send_capacity() - socket.send_queue() - } else { - 0 - }; - - let (bytes_to_external, do_shutdown) = if socket.may_recv() { - if let Ok(data) = socket.peek(TCP_BUFFER_SIZE) { - if data.is_empty() { - (None, false) - } else { - (Some(data), false) - } - } else { - (None, false) - } - } else if !already_shutdown && matches!(socket.state(), State::CloseWait) { - (None, true) - } else { - (None, false) - }; - let selection = if let Some(sleep) = sleeper.take() { - if !do_shutdown { - select! { - biased; - x = ip_receiver.recv() => if let Some(data) = x { - ProxyTcpDataSelect::TxIpPacket(data) - } else { - ProxyTcpDataSelect::Close - }, - x = rx_receiver.recv() => if let Some(data) = x { - ProxyTcpDataSelect::InternalRecv(data) - } else { - ProxyTcpDataSelect::Close - }, - x = external_w.write(bytes_to_external.unwrap_or(b"")), if bytes_to_external.is_some() => ProxyTcpDataSelect::ExternalSent(x?), - x = external_r.read(&mut external_buffer[..bytes_to_client]), if bytes_to_client > 0 => ProxyTcpDataSelect::ExternalRecv(x?), - _ = sleep => ProxyTcpDataSelect::TimePassed, - } - } else { - select! { - biased; - x = ip_receiver.recv() => if let Some(data) = x { - ProxyTcpDataSelect::TxIpPacket(data) - } else { - ProxyTcpDataSelect::Close - }, - x = rx_receiver.recv() => if let Some(data) = x { - ProxyTcpDataSelect::InternalRecv(data) - } else { - ProxyTcpDataSelect::Close - }, - _ = external_w.shutdown() => ProxyTcpDataSelect::ExternalSent(0), - x = external_r.read(&mut external_buffer[..bytes_to_client]), if bytes_to_client > 0 => ProxyTcpDataSelect::ExternalRecv(x?), - _ = sleep => ProxyTcpDataSelect::TimePassed, - } - } - } else if !do_shutdown { - select! { - biased; - x = ip_receiver.recv() => if let Some(data) = x { - ProxyTcpDataSelect::TxIpPacket(data) - } else { - ProxyTcpDataSelect::Close - }, - x = rx_receiver.recv() => if let Some(data) = x { - ProxyTcpDataSelect::InternalRecv(data) - } else { - ProxyTcpDataSelect::Close - }, - x = external_w.write(bytes_to_external.unwrap_or(b"")), if bytes_to_external.is_some() => ProxyTcpDataSelect::ExternalSent(x?), - x = external_r.read(&mut external_buffer[..bytes_to_client]), if bytes_to_client > 0 => ProxyTcpDataSelect::ExternalRecv(x?), - _ = std::future::ready(()) => ProxyTcpDataSelect::DoNothing, - } - } else { - select! { - biased; - x = ip_receiver.recv() => if let Some(data) = x { - ProxyTcpDataSelect::TxIpPacket(data) - } else { - ProxyTcpDataSelect::Close - }, - x = rx_receiver.recv() => if let Some(data) = x { - ProxyTcpDataSelect::InternalRecv(data) - } else { - ProxyTcpDataSelect::Close - }, - _ = external_w.shutdown() => ProxyTcpDataSelect::ExternalSent(0), - x = external_r.read(&mut external_buffer[..bytes_to_client]), if bytes_to_client > 0 => ProxyTcpDataSelect::ExternalRecv(x?), - _ = std::future::ready(()) => ProxyTcpDataSelect::DoNothing, - } - }; - match selection { - ProxyTcpDataSelect::ExternalRecv(size) => { - if size == 0 { - socket.close(); - } else { - socket.send_slice(&external_buffer[..size])?; - } - } - - ProxyTcpDataSelect::ExternalSent(size) => { - if size == 0 { - already_shutdown = true; - } else { - socket.recv(|_| (size, ()))?; - } - } - - ProxyTcpDataSelect::InternalRecv(data) => { - let (_, payload) = Ethernet2Header::from_slice(&data)?; - device.rx = Some(payload.into()); - iface.poll(Instant::now(), &mut device, &mut sockets); - } - - ProxyTcpDataSelect::TxIpPacket(payload) => { - let mut buffer = BytesMut::with_capacity(Ethernet2Header::LEN + payload.len()); - let header = Ethernet2Header { - source: context.key.local_mac.0, - destination: context.key.client_mac.0, - ether_type: match context.key.external_ip.addr { - IpAddress::Ipv4(_) => EtherType::IPV4, - IpAddress::Ipv6(_) => EtherType::IPV6, - }, - }; - buffer.extend_from_slice(&header.to_bytes()); - buffer.extend_from_slice(&payload); - if let Err(error) = context.try_transmit(buffer) { - debug!("failed to transmit tcp packet: {}", error); - } - } - - ProxyTcpDataSelect::TimePassed => { - iface.poll(Instant::now(), &mut device, &mut sockets); - } - - ProxyTcpDataSelect::DoNothing => { - sleeper = Some(tokio::time::sleep(Duration::from_micros(100))); - } - - ProxyTcpDataSelect::Close => { - break; - } - } - } - - let _ = external_socket.shutdown().await; - drop(external_socket); - - loop { - let deadline = tokio::time::sleep(Duration::from_secs(TCP_DANGLE_TIMEOUT_SECS)); - tokio::pin!(deadline); - - let selection = select! { - biased; - x = ip_receiver.recv() => if let Some(data) = x { - ProxyTcpFinishSelect::TxIpPacket(data) - } else { - ProxyTcpFinishSelect::Close - }, - x = rx_receiver.recv() => if let Some(data) = x { - ProxyTcpFinishSelect::InternalRecv(data) - } else { - ProxyTcpFinishSelect::Close - }, - _ = deadline => ProxyTcpFinishSelect::Close, - }; - - match selection { - ProxyTcpFinishSelect::InternalRecv(data) => { - let (_, payload) = Ethernet2Header::from_slice(&data)?; - device.rx = Some(payload.into()); - iface.poll(Instant::now(), &mut device, &mut sockets); - } - - ProxyTcpFinishSelect::TxIpPacket(payload) => { - let mut buffer = BytesMut::with_capacity(Ethernet2Header::LEN + payload.len()); - let header = Ethernet2Header { - source: context.key.local_mac.0, - destination: context.key.client_mac.0, - ether_type: match context.key.external_ip.addr { - IpAddress::Ipv4(_) => EtherType::IPV4, - IpAddress::Ipv6(_) => EtherType::IPV6, - }, - }; - buffer.extend_from_slice(&header.to_bytes()); - buffer.extend_from_slice(&payload); - if let Err(error) = context.try_transmit(buffer) { - debug!("failed to transmit tcp packet: {}", error); - } - } - - ProxyTcpFinishSelect::Close => { - break; - } - } - } - - context.reclaim().await?; - - Ok(()) - } -} diff --git a/crates/network/src/proxynat/udp.rs b/crates/network/src/proxynat/udp.rs deleted file mode 100644 index 9d7a6fca..00000000 --- a/crates/network/src/proxynat/udp.rs +++ /dev/null @@ -1,142 +0,0 @@ -use std::{ - net::{IpAddr, SocketAddr}, - time::Duration, -}; - -use anyhow::{anyhow, Result}; -use async_trait::async_trait; -use bytes::{BufMut, BytesMut}; -use etherparse::{PacketBuilder, SlicedPacket, UdpSlice}; -use log::{debug, warn}; -use smoltcp::wire::IpAddress; -use tokio::{ - io::{AsyncReadExt, AsyncWriteExt}, - select, -}; -use tokio::{sync::mpsc::Receiver, sync::mpsc::Sender}; -use udp_stream::UdpStream; - -use crate::nat::handler::{NatHandler, NatHandlerContext}; - -const UDP_TIMEOUT_SECS: u64 = 60; - -pub struct ProxyUdpHandler { - rx_sender: Sender, -} - -#[async_trait] -impl NatHandler for ProxyUdpHandler { - async fn receive(&self, data: &[u8]) -> Result { - if self.rx_sender.is_closed() { - Ok(true) - } else { - self.rx_sender.try_send(data.into())?; - Ok(true) - } - } -} - -enum ProxyUdpSelect { - External(usize), - Internal(BytesMut), - Close, -} - -impl ProxyUdpHandler { - pub fn new(rx_sender: Sender) -> Self { - ProxyUdpHandler { rx_sender } - } - - pub async fn spawn( - &mut self, - context: NatHandlerContext, - rx_receiver: Receiver, - ) -> Result<()> { - let external_addr = match context.key.external_ip.addr { - IpAddress::Ipv4(addr) => { - SocketAddr::new(IpAddr::V4(addr.0.into()), context.key.external_ip.port) - } - IpAddress::Ipv6(addr) => { - SocketAddr::new(IpAddr::V6(addr.0.into()), context.key.external_ip.port) - } - }; - - let socket = UdpStream::connect(external_addr).await?; - tokio::spawn(async move { - if let Err(error) = ProxyUdpHandler::process(context, socket, rx_receiver).await { - warn!("processing of udp proxy failed: {}", error); - } - }); - Ok(()) - } - - async fn process( - context: NatHandlerContext, - mut socket: UdpStream, - mut rx_receiver: Receiver, - ) -> Result<()> { - let mut external_buffer = vec![0u8; 2048]; - - loop { - let deadline = tokio::time::sleep(Duration::from_secs(UDP_TIMEOUT_SECS)); - let selection = select! { - x = rx_receiver.recv() => if let Some(data) = x { - ProxyUdpSelect::Internal(data) - } else { - ProxyUdpSelect::Close - }, - x = socket.read(&mut external_buffer) => ProxyUdpSelect::External(x?), - _ = deadline => ProxyUdpSelect::Close, - }; - - match selection { - ProxyUdpSelect::External(size) => { - let data = &external_buffer[0..size]; - let packet = - PacketBuilder::ethernet2(context.key.local_mac.0, context.key.client_mac.0); - let packet = match (context.key.external_ip.addr, context.key.client_ip.addr) { - (IpAddress::Ipv4(external_addr), IpAddress::Ipv4(client_addr)) => { - packet.ipv4(external_addr.0, client_addr.0, 20) - } - (IpAddress::Ipv6(external_addr), IpAddress::Ipv6(client_addr)) => { - packet.ipv6(external_addr.0, client_addr.0, 20) - } - _ => { - return Err(anyhow!("IP endpoint mismatch")); - } - }; - let packet = - packet.udp(context.key.external_ip.port, context.key.client_ip.port); - let buffer = BytesMut::with_capacity(packet.size(data.len())); - let mut writer = buffer.writer(); - packet.write(&mut writer, data)?; - let buffer = writer.into_inner(); - if let Err(error) = context.try_transmit(buffer) { - debug!("failed to transmit udp packet: {}", error); - } - } - ProxyUdpSelect::Internal(data) => { - let packet = SlicedPacket::from_ethernet(&data)?; - let Some(ref net) = packet.net else { - continue; - }; - - let Some(ip) = net.ip_payload_ref() else { - continue; - }; - - let udp = UdpSlice::from_slice(ip.payload)?; - socket.write_all(udp.payload()).await?; - } - ProxyUdpSelect::Close => { - drop(socket); - break; - } - } - } - - context.reclaim().await?; - - Ok(()) - } -} diff --git a/crates/network/src/raw_socket.rs b/crates/network/src/raw_socket.rs deleted file mode 100644 index e1389a64..00000000 --- a/crates/network/src/raw_socket.rs +++ /dev/null @@ -1,317 +0,0 @@ -use anyhow::{anyhow, Result}; -use bytes::BytesMut; -use log::{debug, warn}; -use std::io::ErrorKind; -use std::os::fd::{FromRawFd, IntoRawFd}; -use std::os::unix::io::{AsRawFd, RawFd}; -use std::sync::Arc; -use std::{io, mem}; -use tokio::net::UdpSocket; -use tokio::select; -use tokio::sync::mpsc::{channel, Receiver, Sender}; -use tokio::task::JoinHandle; - -const RAW_SOCKET_TRANSMIT_QUEUE_LEN: usize = 3000; -const RAW_SOCKET_RECEIVE_QUEUE_LEN: usize = 3000; - -#[derive(Debug)] -pub enum RawSocketProtocol { - Icmpv4, - Icmpv6, - Ethernet, -} - -impl RawSocketProtocol { - pub fn to_socket_domain(&self) -> i32 { - match self { - RawSocketProtocol::Icmpv4 => libc::AF_INET, - RawSocketProtocol::Icmpv6 => libc::AF_INET6, - RawSocketProtocol::Ethernet => libc::AF_PACKET, - } - } - - pub fn to_socket_protocol(&self) -> u16 { - match self { - RawSocketProtocol::Icmpv4 => libc::IPPROTO_ICMP as u16, - RawSocketProtocol::Icmpv6 => libc::IPPROTO_ICMPV6 as u16, - RawSocketProtocol::Ethernet => (libc::ETH_P_ALL as u16).to_be(), - } - } - - pub fn to_socket_type(&self) -> i32 { - libc::SOCK_RAW - } -} - -const SIOCGIFINDEX: libc::c_ulong = 0x8933; -const SIOCGIFMTU: libc::c_ulong = 0x8921; - -#[derive(Debug)] -pub struct RawSocketHandle { - protocol: RawSocketProtocol, - lower: libc::c_int, -} - -impl AsRawFd for RawSocketHandle { - fn as_raw_fd(&self) -> RawFd { - self.lower - } -} - -impl IntoRawFd for RawSocketHandle { - fn into_raw_fd(self) -> RawFd { - let fd = self.lower; - mem::forget(self); - fd - } -} - -impl RawSocketHandle { - pub fn new(protocol: RawSocketProtocol) -> io::Result { - let lower = unsafe { - let lower = libc::socket( - protocol.to_socket_domain(), - protocol.to_socket_type() | libc::SOCK_NONBLOCK, - protocol.to_socket_protocol() as i32, - ); - if lower == -1 { - return Err(io::Error::last_os_error()); - } - lower - }; - - Ok(RawSocketHandle { protocol, lower }) - } - - pub fn bound_to_interface(interface: &str, protocol: RawSocketProtocol) -> Result { - let mut socket = RawSocketHandle::new(protocol)?; - socket.bind_to_interface(interface)?; - Ok(socket) - } - - pub fn bind_to_interface(&mut self, interface: &str) -> io::Result<()> { - let mut ifreq = ifreq_for(interface); - let sockaddr = libc::sockaddr_ll { - sll_family: libc::AF_PACKET as u16, - sll_protocol: self.protocol.to_socket_protocol(), - sll_ifindex: ifreq_ioctl(self.lower, &mut ifreq, SIOCGIFINDEX)?, - sll_hatype: 1, - sll_pkttype: 0, - sll_halen: 6, - sll_addr: [0; 8], - }; - - unsafe { - let res = libc::bind( - self.lower, - &sockaddr as *const libc::sockaddr_ll as *const libc::sockaddr, - mem::size_of::() as libc::socklen_t, - ); - if res == -1 { - return Err(io::Error::last_os_error()); - } - } - - Ok(()) - } - - pub fn mtu_of_interface(&mut self, interface: &str) -> io::Result { - let mut ifreq = ifreq_for(interface); - ifreq_ioctl(self.lower, &mut ifreq, SIOCGIFMTU).map(|mtu| mtu as usize) - } - - pub fn recv(&self, buffer: &mut [u8]) -> io::Result { - unsafe { - let len = libc::recv( - self.lower, - buffer.as_mut_ptr() as *mut libc::c_void, - buffer.len(), - 0, - ); - if len == -1 { - return Err(io::Error::last_os_error()); - } - Ok(len as usize) - } - } - - pub fn send(&self, buffer: &[u8]) -> io::Result { - unsafe { - let len = libc::send( - self.lower, - buffer.as_ptr() as *const libc::c_void, - buffer.len(), - 0, - ); - if len == -1 { - return Err(io::Error::last_os_error()); - } - Ok(len as usize) - } - } -} - -impl Drop for RawSocketHandle { - fn drop(&mut self) { - unsafe { - libc::close(self.lower); - } - } -} - -#[repr(C)] -#[derive(Debug)] -struct Ifreq { - ifr_name: [libc::c_char; libc::IF_NAMESIZE], - ifr_data: libc::c_int, -} - -fn ifreq_for(name: &str) -> Ifreq { - let mut ifreq = Ifreq { - ifr_name: [0; libc::IF_NAMESIZE], - ifr_data: 0, - }; - for (i, byte) in name.as_bytes().iter().enumerate() { - ifreq.ifr_name[i] = *byte as libc::c_char - } - ifreq -} - -fn ifreq_ioctl( - lower: libc::c_int, - ifreq: &mut Ifreq, - cmd: libc::c_ulong, -) -> io::Result { - unsafe { - let res = libc::ioctl(lower, cmd as _, ifreq as *mut Ifreq); - if res == -1 { - return Err(io::Error::last_os_error()); - } - } - - Ok(ifreq.ifr_data) -} - -pub struct AsyncRawSocketChannel { - pub sender: Sender, - pub receiver: Receiver, - _task: Arc>, -} - -enum AsyncRawSocketChannelSelect { - TransmitPacket(Option), - Readable(()), -} - -impl AsyncRawSocketChannel { - pub fn new(mtu: usize, socket: RawSocketHandle) -> Result { - let (transmit_sender, transmit_receiver) = channel(RAW_SOCKET_TRANSMIT_QUEUE_LEN); - let (receive_sender, receive_receiver) = channel(RAW_SOCKET_RECEIVE_QUEUE_LEN); - let task = AsyncRawSocketChannel::launch(mtu, socket, transmit_receiver, receive_sender)?; - Ok(AsyncRawSocketChannel { - sender: transmit_sender, - receiver: receive_receiver, - _task: Arc::new(task), - }) - } - - fn launch( - mtu: usize, - socket: RawSocketHandle, - transmit_receiver: Receiver, - receive_sender: Sender, - ) -> Result> { - Ok(tokio::task::spawn(async move { - if let Err(error) = - AsyncRawSocketChannel::process(mtu, socket, transmit_receiver, receive_sender).await - { - warn!("failed to process raw socket: {}", error); - } - })) - } - - async fn process( - mtu: usize, - socket: RawSocketHandle, - mut transmit_receiver: Receiver, - receive_sender: Sender, - ) -> Result<()> { - let socket = unsafe { std::net::UdpSocket::from_raw_fd(socket.into_raw_fd()) }; - let socket = UdpSocket::from_std(socket)?; - - let tear_off_size = 100 * mtu; - let mut buffer: BytesMut = BytesMut::with_capacity(tear_off_size); - loop { - if buffer.capacity() < mtu { - buffer = BytesMut::with_capacity(tear_off_size); - } - - let selection = select! { - x = transmit_receiver.recv() => AsyncRawSocketChannelSelect::TransmitPacket(x), - x = socket.readable() => AsyncRawSocketChannelSelect::Readable(x?), - }; - - match selection { - AsyncRawSocketChannelSelect::Readable(_) => { - buffer.resize(mtu, 0); - match socket.try_recv(&mut buffer) { - Ok(len) => { - if len == 0 { - continue; - } - let packet = buffer.split_to(len); - if let Err(error) = receive_sender.try_send(packet) { - debug!( - "failed to process received packet from raw socket: {}", - error - ); - } - } - - Err(ref error) => { - if error.kind() == ErrorKind::WouldBlock { - continue; - } - - // device no longer exists - if error.raw_os_error() == Some(6) { - break; - } - - return Err(anyhow!("failed to read from raw socket: {}", error)); - } - }; - } - - AsyncRawSocketChannelSelect::TransmitPacket(Some(packet)) => { - match socket.try_send(&packet) { - Ok(_len) => {} - Err(ref error) => { - if error.kind() == ErrorKind::WouldBlock { - debug!("failed to transmit: would block"); - continue; - } - - // device no longer exists - if error.raw_os_error() == Some(6) { - break; - } - - return Err(anyhow!( - "failed to write {} bytes to raw socket: {}", - packet.len(), - error - )); - } - }; - } - - AsyncRawSocketChannelSelect::TransmitPacket(None) => { - break; - } - } - } - - Ok(()) - } -} diff --git a/crates/network/src/vbridge.rs b/crates/network/src/vbridge.rs deleted file mode 100644 index 3ce71b96..00000000 --- a/crates/network/src/vbridge.rs +++ /dev/null @@ -1,208 +0,0 @@ -use anyhow::{anyhow, Result}; -use bytes::BytesMut; -use etherparse::{EtherType, Ethernet2Header, IpNumber, Ipv4Header, Ipv6Header, TcpHeader}; -use log::{debug, trace, warn}; -use smoltcp::wire::EthernetAddress; -use std::{ - collections::{hash_map::Entry, HashMap}, - sync::Arc, -}; -use tokio::sync::broadcast::{ - channel as broadcast_channel, Receiver as BroadcastReceiver, Sender as BroadcastSender, -}; -use tokio::{ - select, - sync::{ - mpsc::{channel, Receiver, Sender}, - Mutex, - }, - task::JoinHandle, -}; - -const TO_BRIDGE_QUEUE_LEN: usize = 3000; -const FROM_BRIDGE_QUEUE_LEN: usize = 3000; -const BROADCAST_QUEUE_LEN: usize = 3000; -const MEMBER_LEAVE_QUEUE_LEN: usize = 30; - -#[derive(Debug)] -struct BridgeMember { - pub from_bridge_sender: Sender, -} - -pub struct BridgeJoinHandle { - mac: EthernetAddress, - pub to_bridge_sender: Sender, - pub from_bridge_receiver: Receiver, - pub from_broadcast_receiver: BroadcastReceiver, - member_leave_sender: Sender, -} - -impl Drop for BridgeJoinHandle { - fn drop(&mut self) { - if let Err(error) = self.member_leave_sender.try_send(self.mac) { - warn!( - "virtual bridge member {} failed to leave: {}", - self.mac, error - ); - } - } -} - -type VirtualBridgeMemberMap = Arc>>; - -#[derive(Clone)] -pub struct VirtualBridge { - to_bridge_sender: Sender, - from_broadcast_sender: BroadcastSender, - member_leave_sender: Sender, - members: VirtualBridgeMemberMap, - _task: Arc>, -} - -enum VirtualBridgeSelect { - BroadcastSent, - PacketReceived(Option), - MemberLeave(Option), -} - -impl VirtualBridge { - pub fn new() -> Result { - let (to_bridge_sender, to_bridge_receiver) = channel::(TO_BRIDGE_QUEUE_LEN); - let (member_leave_sender, member_leave_reciever) = - channel::(MEMBER_LEAVE_QUEUE_LEN); - let (from_broadcast_sender, from_broadcast_receiver) = - broadcast_channel(BROADCAST_QUEUE_LEN); - - let members = Arc::new(Mutex::new(HashMap::new())); - let handle = { - let members = members.clone(); - let broadcast_rx_sender = from_broadcast_sender.clone(); - tokio::task::spawn(async move { - if let Err(error) = VirtualBridge::process( - members, - member_leave_reciever, - to_bridge_receiver, - broadcast_rx_sender, - from_broadcast_receiver, - ) - .await - { - warn!("virtual bridge processing task failed: {}", error); - } - }) - }; - - Ok(VirtualBridge { - to_bridge_sender, - from_broadcast_sender, - member_leave_sender, - members, - _task: Arc::new(handle), - }) - } - - pub async fn join(&self, mac: EthernetAddress) -> Result { - let (from_bridge_sender, from_bridge_receiver) = channel::(FROM_BRIDGE_QUEUE_LEN); - let member = BridgeMember { from_bridge_sender }; - - match self.members.lock().await.entry(mac) { - Entry::Occupied(_) => { - return Err(anyhow!("virtual bridge member {} already exists", mac)); - } - Entry::Vacant(entry) => { - entry.insert(member); - } - }; - debug!("virtual bridge member {} has joined", mac); - Ok(BridgeJoinHandle { - mac, - member_leave_sender: self.member_leave_sender.clone(), - from_bridge_receiver, - from_broadcast_receiver: self.from_broadcast_sender.subscribe(), - to_bridge_sender: self.to_bridge_sender.clone(), - }) - } - - async fn process( - members: VirtualBridgeMemberMap, - mut member_leave_reciever: Receiver, - mut to_bridge_receiver: Receiver, - broadcast_rx_sender: BroadcastSender, - mut from_broadcast_receiver: BroadcastReceiver, - ) -> Result<()> { - loop { - let selection = select! { - biased; - x = to_bridge_receiver.recv() => VirtualBridgeSelect::PacketReceived(x), - _ = from_broadcast_receiver.recv() => VirtualBridgeSelect::BroadcastSent, - x = member_leave_reciever.recv() => VirtualBridgeSelect::MemberLeave(x), - }; - - match selection { - VirtualBridgeSelect::PacketReceived(Some(mut packet)) => { - let (header, payload) = match Ethernet2Header::from_slice(&packet) { - Ok(data) => data, - Err(error) => { - debug!("virtual bridge failed to parse ethernet header: {}", error); - continue; - } - }; - - // recalculate TCP checksums when routing packets. - // the xen network backend / frontend drivers for linux - // use checksum offloading but since we bypass some layers - // of the kernel we have to do it ourselves. - if header.ether_type == EtherType::IPV4 { - let (ipv4, payload) = Ipv4Header::from_slice(payload)?; - if ipv4.protocol == IpNumber::TCP { - let (mut tcp, payload) = TcpHeader::from_slice(payload)?; - tcp.checksum = tcp.calc_checksum_ipv4(&ipv4, payload)?; - let tcp_header_offset = Ethernet2Header::LEN + ipv4.header_len(); - let mut header = &mut packet[tcp_header_offset..]; - tcp.write(&mut header)?; - } - } else if header.ether_type == EtherType::IPV6 { - let (ipv6, payload) = Ipv6Header::from_slice(payload)?; - if ipv6.next_header == IpNumber::TCP { - let (mut tcp, payload) = TcpHeader::from_slice(payload)?; - tcp.checksum = tcp.calc_checksum_ipv6(&ipv6, payload)?; - let tcp_header_offset = Ethernet2Header::LEN + ipv6.header_len(); - let mut header = &mut packet[tcp_header_offset..]; - tcp.write(&mut header)?; - } - } - - let destination = EthernetAddress(header.destination); - if destination.is_multicast() { - broadcast_rx_sender.send(packet)?; - continue; - } - match members.lock().await.get(&destination) { - Some(member) => { - member.from_bridge_sender.try_send(packet)?; - trace!( - "sending bridged packet from {} to {}", - EthernetAddress(header.source), - EthernetAddress(header.destination) - ); - } - None => { - trace!("no bridge member with address: {}", destination); - } - } - } - - VirtualBridgeSelect::MemberLeave(Some(mac)) => { - if members.lock().await.remove(&mac).is_some() { - debug!("virtual bridge member {} has left", mac); - } - } - - VirtualBridgeSelect::PacketReceived(None) => break, - VirtualBridgeSelect::MemberLeave(None) => {} - VirtualBridgeSelect::BroadcastSent => {} - } - } - Ok(()) - } -} diff --git a/crates/oci/Cargo.toml b/crates/oci/Cargo.toml deleted file mode 100644 index fd9ecb1d..00000000 --- a/crates/oci/Cargo.toml +++ /dev/null @@ -1,41 +0,0 @@ -[package] -name = "krata-oci" -description = "OCI services for the krata isolation engine" -license.workspace = true -version.workspace = true -homepage.workspace = true -repository.workspace = true -edition = "2021" -resolver = "2" - -[dependencies] -anyhow = { workspace = true } -async-compression = { workspace = true, features = ["tokio", "gzip", "zstd"] } -async-trait = { workspace = true } -backhand = { workspace = true } -bytes = { workspace = true } -indexmap = { workspace = true } -krata-tokio-tar = { workspace = true } -log = { workspace = true } -oci-spec = { workspace = true } -path-clean = { workspace = true } -reqwest = { workspace = true } -scopeguard = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -sha256 = { workspace = true } -tokio = { workspace = true } -tokio-stream = { workspace = true } -url = { workspace = true } -uuid = { workspace = true } -walkdir = { workspace = true } - -[lib] -name = "krataoci" - -[dev-dependencies] -env_logger = { workspace = true } - -[[example]] -name = "krataoci-squashify" -path = "examples/squashify.rs" diff --git a/crates/oci/examples/squashify.rs b/crates/oci/examples/squashify.rs deleted file mode 100644 index c1d24d76..00000000 --- a/crates/oci/examples/squashify.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::{env::args, path::PathBuf}; - -use anyhow::Result; -use env_logger::Env; -use krataoci::{ - name::ImageName, - packer::{service::OciPackerService, OciPackedFormat}, - progress::OciProgressContext, - registry::OciPlatform, -}; -use tokio::fs; - -#[tokio::main] -async fn main() -> Result<()> { - env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); - - let image = ImageName::parse(&args().nth(1).unwrap())?; - let seed = args().nth(2).map(PathBuf::from); - - let cache_dir = PathBuf::from("krata-cache"); - if !cache_dir.exists() { - fs::create_dir(&cache_dir).await?; - } - - let (context, mut receiver) = OciProgressContext::create(); - tokio::task::spawn(async move { - loop { - if receiver.changed().await.is_err() { - break; - } - let progress = receiver.borrow_and_update(); - println!("phase {:?}", progress.phase); - for (id, layer) in &progress.layers { - println!("{} {:?} {:?}", id, layer.phase, layer.indication,) - } - } - }); - let service = OciPackerService::new(seed, &cache_dir, OciPlatform::current()).await?; - let packed = service - .request( - image.clone(), - OciPackedFormat::Squashfs, - false, - true, - context, - ) - .await?; - println!( - "generated squashfs of {} to {}", - image, - packed.path.to_string_lossy() - ); - Ok(()) -} diff --git a/crates/oci/src/assemble.rs b/crates/oci/src/assemble.rs deleted file mode 100644 index 97c89e54..00000000 --- a/crates/oci/src/assemble.rs +++ /dev/null @@ -1,273 +0,0 @@ -use crate::fetch::{OciImageFetcher, OciImageLayer, OciImageLayerReader, OciResolvedImage}; -use crate::progress::OciBoundProgress; -use crate::schema::OciSchema; -use crate::vfs::{VfsNode, VfsTree}; -use anyhow::{anyhow, Result}; -use log::{debug, trace, warn}; -use oci_spec::image::{Descriptor, ImageConfiguration, ImageManifest}; - -use std::path::{Path, PathBuf}; -use std::pin::Pin; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use tokio::fs; -use tokio_stream::StreamExt; -use tokio_tar::{Archive, Entry}; -use uuid::Uuid; - -pub struct OciImageAssembled { - pub digest: String, - pub descriptor: Descriptor, - pub manifest: OciSchema, - pub config: OciSchema, - pub vfs: Arc, - pub tmp_dir: Option, -} - -impl Drop for OciImageAssembled { - fn drop(&mut self) { - if let Some(tmp) = self.tmp_dir.clone() { - tokio::task::spawn(async move { - let _ = fs::remove_dir_all(&tmp).await; - }); - } - } -} - -pub struct OciImageAssembler { - downloader: OciImageFetcher, - resolved: Option, - progress: OciBoundProgress, - work_dir: PathBuf, - disk_dir: PathBuf, - tmp_dir: Option, - success: AtomicBool, -} - -impl OciImageAssembler { - pub async fn new( - downloader: OciImageFetcher, - resolved: OciResolvedImage, - progress: OciBoundProgress, - work_dir: Option, - disk_dir: Option, - ) -> Result { - let tmp_dir = if work_dir.is_none() || disk_dir.is_none() { - let mut tmp_dir = std::env::temp_dir().clone(); - tmp_dir.push(format!("oci-assemble-{}", Uuid::new_v4())); - Some(tmp_dir) - } else { - None - }; - - let work_dir = if let Some(work_dir) = work_dir { - work_dir - } else { - let mut tmp_dir = tmp_dir - .clone() - .ok_or(anyhow!("tmp_dir was not created when expected"))?; - tmp_dir.push("work"); - tmp_dir - }; - - let target_dir = if let Some(target_dir) = disk_dir { - target_dir - } else { - let mut tmp_dir = tmp_dir - .clone() - .ok_or(anyhow!("tmp_dir was not created when expected"))?; - tmp_dir.push("image"); - tmp_dir - }; - - fs::create_dir_all(&work_dir).await?; - fs::create_dir_all(&target_dir).await?; - - Ok(OciImageAssembler { - downloader, - resolved: Some(resolved), - progress, - work_dir, - disk_dir: target_dir, - tmp_dir, - success: AtomicBool::new(false), - }) - } - - pub async fn assemble(self) -> Result { - debug!("assemble"); - let mut layer_dir = self.work_dir.clone(); - layer_dir.push("layer"); - fs::create_dir_all(&layer_dir).await?; - self.assemble_with(&layer_dir).await - } - - async fn assemble_with(mut self, layer_dir: &Path) -> Result { - let Some(ref resolved) = self.resolved else { - return Err(anyhow!("resolved image was not available when expected")); - }; - let local = self.downloader.download(resolved, layer_dir).await?; - let mut vfs = VfsTree::new(); - for layer in &local.layers { - debug!( - "process layer digest={} compression={:?}", - &layer.digest, layer.compression, - ); - self.progress - .update(|progress| { - progress.start_extracting_layer(&layer.digest); - }) - .await; - debug!("process layer digest={}", &layer.digest,); - let mut archive = layer.archive().await?; - let mut entries = archive.entries()?; - let mut count = 0u64; - let mut size = 0u64; - while let Some(entry) = entries.next().await { - let mut entry = entry?; - let path = entry.path()?; - let Some(name) = path.file_name() else { - continue; - }; - let Some(name) = name.to_str() else { - continue; - }; - if name.starts_with(".wh.") { - self.process_whiteout_entry(&mut vfs, &entry, name, layer) - .await?; - } else { - let reference = vfs.insert_tar_entry(&entry)?; - self.progress - .update(|progress| { - progress.extracting_layer(&layer.digest, &reference.name); - }) - .await; - size += self - .process_write_entry(&mut vfs, &mut entry, layer) - .await?; - count += 1; - } - } - self.progress - .update(|progress| { - progress.extracted_layer(&layer.digest, count, size); - }) - .await; - } - for layer in &local.layers { - if layer.path.exists() { - fs::remove_file(&layer.path).await?; - } - } - - let Some(resolved) = self.resolved.take() else { - return Err(anyhow!("resolved image was not available when expected")); - }; - - let assembled = OciImageAssembled { - vfs: Arc::new(vfs), - descriptor: resolved.descriptor, - digest: resolved.digest, - manifest: resolved.manifest, - config: local.config, - tmp_dir: self.tmp_dir.clone(), - }; - self.success.store(true, Ordering::Release); - Ok(assembled) - } - - async fn process_whiteout_entry( - &self, - vfs: &mut VfsTree, - entry: &Entry>>>, - name: &str, - layer: &OciImageLayer, - ) -> Result<()> { - let path = entry.path()?; - let mut path = path.to_path_buf(); - path.pop(); - - let opaque = name == ".wh..wh..opq"; - - if !opaque { - let file = &name[4..]; - path.push(file); - } - - trace!( - "whiteout entry {:?} layer={} path={:?}", - entry.path()?, - &layer.digest, - path - ); - - let result = vfs.root.remove(&path); - if let Some((parent, mut removed)) = result { - delete_disk_paths(&removed).await?; - if opaque { - removed.children.clear(); - parent.children.push(removed); - } - } else { - warn!( - "whiteout entry layer={} path={:?} did not exist", - &layer.digest, path - ); - } - Ok(()) - } - - async fn process_write_entry( - &self, - vfs: &mut VfsTree, - entry: &mut Entry>>>, - layer: &OciImageLayer, - ) -> Result { - if !entry.header().entry_type().is_file() { - return Ok(0); - } - trace!( - "unpack entry layer={} path={:?} type={:?}", - &layer.digest, - entry.path()?, - entry.header().entry_type(), - ); - entry.set_preserve_permissions(false); - entry.set_unpack_xattrs(false); - entry.set_preserve_mtime(false); - let path = entry - .unpack_in(&self.disk_dir) - .await? - .ok_or(anyhow!("unpack did not return a path"))?; - vfs.set_disk_path(&entry.path()?, &path)?; - Ok(entry.header().size()?) - } -} - -impl Drop for OciImageAssembler { - fn drop(&mut self) { - if !self.success.load(Ordering::Acquire) { - if let Some(tmp_dir) = self.tmp_dir.clone() { - tokio::task::spawn(async move { - let _ = fs::remove_dir_all(tmp_dir).await; - }); - } - } - } -} - -async fn delete_disk_paths(node: &VfsNode) -> Result<()> { - let mut queue = vec![node]; - while !queue.is_empty() { - let node = queue.remove(0); - if let Some(ref disk_path) = node.disk_path { - if !disk_path.exists() { - warn!("disk path {:?} does not exist", disk_path); - } - fs::remove_file(disk_path).await?; - } - let children = node.children.iter().collect::>(); - queue.extend_from_slice(&children); - } - Ok(()) -} diff --git a/crates/oci/src/fetch.rs b/crates/oci/src/fetch.rs deleted file mode 100644 index 1795f125..00000000 --- a/crates/oci/src/fetch.rs +++ /dev/null @@ -1,381 +0,0 @@ -use crate::{ - progress::{OciBoundProgress, OciProgressPhase}, - schema::OciSchema, -}; - -use super::{ - name::ImageName, - registry::{OciPlatform, OciRegistryClient}, -}; - -use std::{ - fmt::Debug, - io::SeekFrom, - os::unix::fs::MetadataExt, - path::{Path, PathBuf}, - pin::Pin, -}; - -use anyhow::{anyhow, Result}; -use async_compression::tokio::bufread::{GzipDecoder, ZstdDecoder}; -use log::debug; -use oci_spec::image::{ - Descriptor, DescriptorBuilder, ImageConfiguration, ImageIndex, ImageManifest, MediaType, - ToDockerV2S2, -}; -use serde::de::DeserializeOwned; -use tokio::{ - fs::{self, File}, - io::{AsyncRead, AsyncReadExt, AsyncSeekExt, BufReader, BufWriter}, -}; -use tokio_stream::StreamExt; -use tokio_tar::Archive; - -pub struct OciImageFetcher { - seed: Option, - platform: OciPlatform, - progress: OciBoundProgress, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum OciImageLayerCompression { - None, - Gzip, - Zstd, -} - -#[derive(Clone, Debug)] -pub struct OciImageLayer { - pub metadata: Descriptor, - pub path: PathBuf, - pub digest: String, - pub compression: OciImageLayerCompression, -} - -#[async_trait::async_trait] -pub trait OciImageLayerReader: AsyncRead + Sync { - async fn position(&mut self) -> Result; -} - -#[async_trait::async_trait] -impl OciImageLayerReader for BufReader { - async fn position(&mut self) -> Result { - Ok(self.seek(SeekFrom::Current(0)).await?) - } -} - -#[async_trait::async_trait] -impl OciImageLayerReader for GzipDecoder> { - async fn position(&mut self) -> Result { - self.get_mut().position().await - } -} - -#[async_trait::async_trait] -impl OciImageLayerReader for ZstdDecoder> { - async fn position(&mut self) -> Result { - self.get_mut().position().await - } -} - -impl OciImageLayer { - pub async fn decompress(&self) -> Result>> { - let file = File::open(&self.path).await?; - let reader = BufReader::new(file); - let reader: Pin> = match self.compression { - OciImageLayerCompression::None => Box::pin(reader), - OciImageLayerCompression::Gzip => Box::pin(GzipDecoder::new(reader)), - OciImageLayerCompression::Zstd => Box::pin(ZstdDecoder::new(reader)), - }; - Ok(reader) - } - - pub async fn archive(&self) -> Result>>> { - let decompress = self.decompress().await?; - Ok(Archive::new(decompress)) - } -} - -#[derive(Clone, Debug)] -pub struct OciResolvedImage { - pub name: ImageName, - pub digest: String, - pub descriptor: Descriptor, - pub manifest: OciSchema, -} - -#[derive(Clone, Debug)] -pub struct OciLocalImage { - pub image: OciResolvedImage, - pub config: OciSchema, - pub layers: Vec, -} - -impl OciImageFetcher { - pub fn new( - seed: Option, - platform: OciPlatform, - progress: OciBoundProgress, - ) -> OciImageFetcher { - OciImageFetcher { - seed, - platform, - progress, - } - } - - async fn load_seed_json_blob( - &self, - descriptor: &Descriptor, - ) -> Result>> { - let digest = descriptor.digest(); - let Some((digest_type, digest_content)) = digest.split_once(':') else { - return Err(anyhow!("digest content was not properly formatted")); - }; - let want = format!("blobs/{}/{}", digest_type, digest_content); - self.load_seed_json(&want).await - } - - async fn load_seed_json( - &self, - want: &str, - ) -> Result>> { - let Some(ref seed) = self.seed else { - return Ok(None); - }; - - let file = File::open(seed).await?; - let mut archive = Archive::new(file); - let mut entries = archive.entries()?; - while let Some(entry) = entries.next().await { - let mut entry = entry?; - let path = String::from_utf8(entry.path_bytes().to_vec())?; - if path == want { - let mut content = Vec::new(); - entry.read_to_end(&mut content).await?; - let item = serde_json::from_slice::(&content)?; - return Ok(Some(OciSchema::new(content, item))); - } - } - Ok(None) - } - - async fn extract_seed_blob(&self, descriptor: &Descriptor, to: &Path) -> Result { - let Some(ref seed) = self.seed else { - return Ok(false); - }; - - let digest = descriptor.digest(); - let Some((digest_type, digest_content)) = digest.split_once(':') else { - return Err(anyhow!("digest content was not properly formatted")); - }; - let want = format!("blobs/{}/{}", digest_type, digest_content); - - let seed = File::open(seed).await?; - let mut archive = Archive::new(seed); - let mut entries = archive.entries()?; - while let Some(entry) = entries.next().await { - let mut entry = entry?; - let path = String::from_utf8(entry.path_bytes().to_vec())?; - if path == want { - let file = File::create(to).await?; - let mut bufwrite = BufWriter::new(file); - tokio::io::copy(&mut entry, &mut bufwrite).await?; - return Ok(true); - } - } - Ok(false) - } - - pub async fn resolve(&self, image: ImageName) -> Result { - debug!("resolve manifest image={}", image); - - if let Some(index) = self.load_seed_json::("index.json").await? { - let mut found: Option<&Descriptor> = None; - for manifest in index.item().manifests() { - let Some(annotations) = manifest.annotations() else { - continue; - }; - - let mut image_name = annotations.get("io.containerd.image.name"); - if image_name.is_none() { - image_name = annotations.get("org.opencontainers.image.ref.name"); - } - - let Some(image_name) = image_name else { - continue; - }; - - if *image_name != image.to_string() { - continue; - } - - if let Some(platform) = manifest.platform() { - if *platform.architecture() != self.platform.arch - || *platform.os() != self.platform.os - { - continue; - } - } - - if let Some(ref digest) = image.digest { - if digest != manifest.digest() { - continue; - } - } - - found = Some(manifest); - break; - } - - if let Some(found) = found { - if let Some(manifest) = self.load_seed_json_blob(found).await? { - debug!( - "found seeded manifest image={} manifest={}", - image, - found.digest() - ); - return Ok(OciResolvedImage { - name: image, - descriptor: found.clone(), - digest: found.digest().clone(), - manifest, - }); - } - } - } - - let mut client = OciRegistryClient::new(image.registry_url()?, self.platform.clone())?; - let (manifest, descriptor, digest) = client - .get_manifest_with_digest(&image.name, image.reference.as_ref(), image.digest.as_ref()) - .await?; - let descriptor = descriptor.unwrap_or_else(|| { - DescriptorBuilder::default() - .media_type(MediaType::ImageManifest) - .size(manifest.raw().len() as i64) - .digest(digest.clone()) - .build() - .unwrap() - }); - Ok(OciResolvedImage { - name: image, - descriptor, - digest, - manifest, - }) - } - - pub async fn download( - &self, - image: &OciResolvedImage, - layer_dir: &Path, - ) -> Result { - let config: OciSchema; - self.progress - .update(|progress| { - progress.phase = OciProgressPhase::ConfigDownload; - }) - .await; - let mut client = OciRegistryClient::new(image.name.registry_url()?, self.platform.clone())?; - if let Some(seeded) = self - .load_seed_json_blob::(image.manifest.item().config()) - .await? - { - config = seeded; - } else { - let config_bytes = client - .get_blob(&image.name.name, image.manifest.item().config()) - .await?; - config = OciSchema::new( - config_bytes.to_vec(), - serde_json::from_slice(&config_bytes)?, - ); - } - self.progress - .update(|progress| { - progress.phase = OciProgressPhase::LayerDownload; - - for layer in image.manifest.item().layers() { - progress.add_layer(layer.digest()); - } - }) - .await; - let mut layers = Vec::new(); - for layer in image.manifest.item().layers() { - self.progress - .update(|progress| { - progress.downloading_layer(layer.digest(), 0, layer.size() as u64); - }) - .await; - layers.push( - self.acquire_layer(&image.name, layer, layer_dir, &mut client) - .await?, - ); - self.progress - .update(|progress| { - progress.downloaded_layer(layer.digest(), layer.size() as u64); - }) - .await; - } - Ok(OciLocalImage { - image: image.clone(), - config, - layers, - }) - } - - async fn acquire_layer( - &self, - image: &ImageName, - layer: &Descriptor, - layer_dir: &Path, - client: &mut OciRegistryClient, - ) -> Result { - debug!( - "acquire layer digest={} size={}", - layer.digest(), - layer.size() - ); - let mut layer_path = layer_dir.to_path_buf(); - layer_path.push(format!("{}.layer", layer.digest())); - - let seeded = self.extract_seed_blob(layer, &layer_path).await?; - if !seeded { - let file = File::create(&layer_path).await?; - let size = client - .write_blob_to_file(&image.name, layer, file, Some(self.progress.clone())) - .await?; - if layer.size() as u64 != size { - return Err(anyhow!( - "downloaded layer size differs from size in manifest", - )); - } - } - - let metadata = fs::metadata(&layer_path).await?; - - if layer.size() as u64 != metadata.size() { - return Err(anyhow!("layer size differs from size in manifest",)); - } - - let mut media_type = layer.media_type().clone(); - - // docker layer compatibility - if media_type.to_string() == MediaType::ImageLayerGzip.to_docker_v2s2()? { - media_type = MediaType::ImageLayerGzip; - } - - let compression = match media_type { - MediaType::ImageLayer => OciImageLayerCompression::None, - MediaType::ImageLayerGzip => OciImageLayerCompression::Gzip, - MediaType::ImageLayerZstd => OciImageLayerCompression::Zstd, - other => return Err(anyhow!("found layer with unknown media type: {}", other)), - }; - Ok(OciImageLayer { - metadata: layer.clone(), - path: layer_path, - digest: layer.digest().clone(), - compression, - }) - } -} diff --git a/crates/oci/src/lib.rs b/crates/oci/src/lib.rs deleted file mode 100644 index de3f17d9..00000000 --- a/crates/oci/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod assemble; -pub mod fetch; -pub mod name; -pub mod packer; -pub mod progress; -pub mod registry; -pub mod schema; -pub mod vfs; diff --git a/crates/oci/src/name.rs b/crates/oci/src/name.rs deleted file mode 100644 index b1438f0b..00000000 --- a/crates/oci/src/name.rs +++ /dev/null @@ -1,147 +0,0 @@ -use anyhow::Result; -use std::fmt; -use url::Url; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ImageName { - pub hostname: String, - pub port: Option, - pub name: String, - pub reference: Option, - pub digest: Option, -} - -impl fmt::Display for ImageName { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut suffix = String::new(); - - if let Some(ref reference) = self.reference { - suffix.push(':'); - suffix.push_str(reference); - } - - if let Some(ref digest) = self.digest { - suffix.push('@'); - suffix.push_str(digest); - } - - if ImageName::DOCKER_HUB_MIRROR == self.hostname && self.port.is_none() { - if self.name.starts_with("library/") { - write!(f, "{}{}", &self.name[8..], suffix) - } else { - write!(f, "{}{}", self.name, suffix) - } - } else if let Some(port) = self.port { - write!(f, "{}:{}/{}{}", self.hostname, port, self.name, suffix) - } else { - write!(f, "{}/{}{}", self.hostname, self.name, suffix) - } - } -} - -impl Default for ImageName { - fn default() -> Self { - Self::parse(&format!("{}", uuid::Uuid::new_v4().as_hyphenated())) - .expect("UUID hyphenated must be valid name") - } -} - -impl ImageName { - pub const DOCKER_HUB_MIRROR: &'static str = "mirror.gcr.io"; - pub const DEFAULT_IMAGE_TAG: &'static str = "latest"; - - pub fn parse(name: &str) -> Result { - let full_name = name.to_string(); - let name = full_name.clone(); - let (mut hostname, mut name) = name - .split_once('/') - .map(|x| (x.0.to_string(), x.1.to_string())) - .unwrap_or_else(|| { - ( - ImageName::DOCKER_HUB_MIRROR.to_string(), - format!("library/{}", name), - ) - }); - - // heuristic to find any docker hub image formats - // that may be in the hostname format. for example: - // abc/xyz:latest will trigger this if check, but abc.io/xyz:latest will not, - // and neither will abc/hello/xyz:latest - if !hostname.contains('.') && full_name.chars().filter(|x| *x == '/').count() == 1 { - name = format!("{}/{}", hostname, name); - hostname = ImageName::DOCKER_HUB_MIRROR.to_string(); - } - - let (hostname, port) = if let Some((hostname, port)) = hostname - .split_once(':') - .map(|x| (x.0.to_string(), x.1.to_string())) - { - (hostname, Some(str::parse(&port)?)) - } else { - (hostname, None) - }; - - let name_has_digest = if name.contains('@') { - let digest_start = name.chars().position(|c| c == '@'); - let ref_start = name.chars().position(|c| c == ':'); - if let (Some(digest_start), Some(ref_start)) = (digest_start, ref_start) { - digest_start < ref_start - } else { - true - } - } else { - false - }; - - let (name, digest) = if name_has_digest { - name.split_once('@') - .map(|(name, digest)| (name.to_string(), Some(digest.to_string()))) - .unwrap_or_else(|| (name, None)) - } else { - (name, None) - }; - - let (name, reference) = if name.contains(':') { - name.split_once(':') - .map(|(name, reference)| (name.to_string(), Some(reference.to_string()))) - .unwrap_or((name, None)) - } else { - (name, None) - }; - - let (reference, digest) = if let Some(reference) = reference { - if let Some(digest) = digest { - (Some(reference), Some(digest)) - } else { - reference - .split_once('@') - .map(|(reff, digest)| (Some(reff.to_string()), Some(digest.to_string()))) - .unwrap_or_else(|| (Some(reference), None)) - } - } else { - (None, digest) - }; - - Ok(ImageName { - hostname, - port, - name, - reference, - digest, - }) - } - - pub fn registry_url(&self) -> Result { - let hostname = if let Some(port) = self.port { - format!("{}:{}", self.hostname, port) - } else { - self.hostname.clone() - }; - let url = if self.hostname.starts_with("localhost") { - format!("http://{}", hostname) - } else { - format!("https://{}", hostname) - }; - Ok(Url::parse(&url)?) - } -} diff --git a/crates/oci/src/packer/backend.rs b/crates/oci/src/packer/backend.rs deleted file mode 100644 index 63b838b5..00000000 --- a/crates/oci/src/packer/backend.rs +++ /dev/null @@ -1,236 +0,0 @@ -use std::{os::unix::fs::MetadataExt, path::Path, process::Stdio, sync::Arc}; - -use super::OciPackedFormat; -use crate::{progress::OciBoundProgress, vfs::VfsTree}; -use anyhow::{anyhow, Result}; -use log::warn; -use tokio::{ - fs::{self, File}, - io::BufWriter, - pin, - process::{Child, Command}, - select, -}; - -#[derive(Debug, Clone, Copy)] -pub enum OciPackerBackendType { - MkSquashfs, - MkfsErofs, - Tar, -} - -impl OciPackerBackendType { - pub fn format(&self) -> OciPackedFormat { - match self { - OciPackerBackendType::MkSquashfs => OciPackedFormat::Squashfs, - OciPackerBackendType::MkfsErofs => OciPackedFormat::Erofs, - OciPackerBackendType::Tar => OciPackedFormat::Tar, - } - } - - pub fn create(&self) -> Box { - match self { - OciPackerBackendType::MkSquashfs => { - Box::new(OciPackerMkSquashfs {}) as Box - } - OciPackerBackendType::MkfsErofs => { - Box::new(OciPackerMkfsErofs {}) as Box - } - OciPackerBackendType::Tar => Box::new(OciPackerTar {}) as Box, - } - } -} - -#[async_trait::async_trait] -pub trait OciPackerBackend: Send + Sync { - async fn pack(&self, progress: OciBoundProgress, vfs: Arc, file: &Path) -> Result<()>; -} - -pub struct OciPackerMkSquashfs {} - -#[async_trait::async_trait] -impl OciPackerBackend for OciPackerMkSquashfs { - async fn pack(&self, progress: OciBoundProgress, vfs: Arc, file: &Path) -> Result<()> { - progress - .update(|progress| { - progress.start_packing(); - }) - .await; - - let child = Command::new("mksquashfs") - .arg("-") - .arg(file) - .arg("-comp") - .arg("gzip") - .arg("-tar") - .stdin(Stdio::piped()) - .stderr(Stdio::null()) - .stdout(Stdio::null()) - .spawn()?; - let mut child = ChildProcessKillGuard(child); - let stdin = child - .0 - .stdin - .take() - .ok_or(anyhow!("unable to acquire stdin stream"))?; - let mut writer = Some(tokio::task::spawn(async move { - if let Err(error) = vfs.write_to_tar(stdin).await { - warn!("failed to write tar: {}", error); - return Err(error); - } - Ok(()) - })); - let wait = child.0.wait(); - pin!(wait); - let status_result = loop { - if let Some(inner) = writer.as_mut() { - select! { - x = inner => { - writer = None; - match x { - Ok(_) => {}, - Err(error) => { - return Err(error.into()); - } - } - }, - status = &mut wait => { - break status; - } - } - } else { - select! { - status = &mut wait => { - break status; - } - } - } - }; - if let Some(writer) = writer { - writer.await??; - } - let status = status_result?; - if !status.success() { - Err(anyhow!( - "mksquashfs failed with exit code: {}", - status.code().unwrap() - )) - } else { - let metadata = fs::metadata(&file).await?; - progress - .update(|progress| progress.complete(metadata.size())) - .await; - Ok(()) - } - } -} - -pub struct OciPackerMkfsErofs {} - -#[async_trait::async_trait] -impl OciPackerBackend for OciPackerMkfsErofs { - async fn pack(&self, progress: OciBoundProgress, vfs: Arc, file: &Path) -> Result<()> { - progress - .update(|progress| { - progress.start_packing(); - }) - .await; - - let child = Command::new("mkfs.erofs") - .arg("-L") - .arg("root") - .arg("--tar=-") - .arg(file) - .stdin(Stdio::piped()) - .stderr(Stdio::null()) - .stdout(Stdio::null()) - .spawn()?; - let mut child = ChildProcessKillGuard(child); - let stdin = child - .0 - .stdin - .take() - .ok_or(anyhow!("unable to acquire stdin stream"))?; - let mut writer = Some(tokio::task::spawn( - async move { vfs.write_to_tar(stdin).await }, - )); - let wait = child.0.wait(); - pin!(wait); - let status_result = loop { - if let Some(inner) = writer.as_mut() { - select! { - x = inner => { - match x { - Ok(_) => { - writer = None; - }, - Err(error) => { - return Err(error.into()); - } - } - }, - status = &mut wait => { - break status; - } - } - } else { - select! { - status = &mut wait => { - break status; - } - } - } - }; - if let Some(writer) = writer { - writer.await??; - } - let status = status_result?; - if !status.success() { - Err(anyhow!( - "mkfs.erofs failed with exit code: {}", - status.code().unwrap() - )) - } else { - let metadata = fs::metadata(&file).await?; - progress - .update(|progress| { - progress.complete(metadata.size()); - }) - .await; - Ok(()) - } - } -} - -pub struct OciPackerTar {} - -#[async_trait::async_trait] -impl OciPackerBackend for OciPackerTar { - async fn pack(&self, progress: OciBoundProgress, vfs: Arc, file: &Path) -> Result<()> { - progress - .update(|progress| { - progress.start_packing(); - }) - .await; - - let output = File::create(file).await?; - let output = BufWriter::new(output); - vfs.write_to_tar(output).await?; - - let metadata = fs::metadata(file).await?; - progress - .update(|progress| { - progress.complete(metadata.size()); - }) - .await; - Ok(()) - } -} - -struct ChildProcessKillGuard(Child); - -impl Drop for ChildProcessKillGuard { - fn drop(&mut self) { - let _ = self.0.start_kill(); - } -} diff --git a/crates/oci/src/packer/cache.rs b/crates/oci/src/packer/cache.rs deleted file mode 100644 index f056713e..00000000 --- a/crates/oci/src/packer/cache.rs +++ /dev/null @@ -1,266 +0,0 @@ -use crate::{ - name::ImageName, - packer::{OciPackedFormat, OciPackedImage}, - schema::OciSchema, -}; - -use crate::fetch::OciResolvedImage; -use anyhow::Result; -use log::{debug, error}; -use oci_spec::image::{ - Descriptor, ImageConfiguration, ImageIndex, ImageIndexBuilder, ImageManifest, MediaType, - ANNOTATION_REF_NAME, -}; -use std::{ - path::{Path, PathBuf}, - sync::Arc, -}; -use tokio::{fs, sync::RwLock}; - -#[derive(Clone)] -pub struct OciPackerCache { - cache_dir: PathBuf, - index: Arc>, -} - -const ANNOTATION_IMAGE_NAME: &str = "io.containerd.image.name"; -const ANNOTATION_OCI_PACKER_FORMAT: &str = "dev.krata.oci.packer.format"; - -impl OciPackerCache { - pub async fn new(cache_dir: &Path) -> Result { - let index = ImageIndexBuilder::default() - .schema_version(2u32) - .media_type(MediaType::ImageIndex) - .manifests(Vec::new()) - .build()?; - let cache = OciPackerCache { - cache_dir: cache_dir.to_path_buf(), - index: Arc::new(RwLock::new(index)), - }; - - { - let mut mutex = cache.index.write().await; - *mutex = cache.load_index().await?; - } - - Ok(cache) - } - - pub async fn list(&self) -> Result> { - let index = self.index.read().await; - Ok(index.manifests().clone()) - } - - pub async fn resolve( - &self, - name: ImageName, - format: OciPackedFormat, - ) -> Result> { - if name.reference.as_deref() == Some("latest") { - return Ok(None); - } - let name_str = name.to_string(); - let index = self.index.read().await; - let mut descriptor: Option = None; - for manifest in index.manifests() { - let Some(name) = manifest - .annotations() - .clone() - .unwrap_or_default() - .get(ANNOTATION_IMAGE_NAME) - .cloned() - else { - continue; - }; - - if name == name_str { - descriptor = Some(manifest.clone()); - } - } - - let Some(descriptor) = descriptor else { - return Ok(None); - }; - - debug!("resolve hit name={} digest={}", name, descriptor.digest()); - - self.recall(name, descriptor.digest().as_ref(), format) - .await - .map(|image| { - image.map(|i| OciResolvedImage { - name: i.name, - digest: i.digest, - descriptor: i.descriptor, - manifest: i.manifest, - }) - }) - } - - pub async fn recall( - &self, - name: ImageName, - digest: &str, - format: OciPackedFormat, - ) -> Result> { - let index = self.index.read().await; - - let mut descriptor: Option = None; - for manifest in index.manifests() { - if manifest.digest() == digest - && manifest - .annotations() - .as_ref() - .and_then(|x| x.get(ANNOTATION_OCI_PACKER_FORMAT)) - .map(|x| x.as_str()) - == Some(format.extension()) - { - descriptor = Some(manifest.clone()); - break; - } - } - - let Some(descriptor) = descriptor else { - return Ok(None); - }; - - let mut fs_path = self.cache_dir.clone(); - let mut config_path = self.cache_dir.clone(); - let mut manifest_path = self.cache_dir.clone(); - fs_path.push(format!("{}.{}", digest, format.extension())); - manifest_path.push(format!("{}.manifest.json", digest)); - config_path.push(format!("{}.config.json", digest)); - - if fs_path.exists() && manifest_path.exists() && config_path.exists() { - let image_metadata = fs::metadata(&fs_path).await?; - let manifest_metadata = fs::metadata(&manifest_path).await?; - let config_metadata = fs::metadata(&config_path).await?; - if image_metadata.is_file() && manifest_metadata.is_file() && config_metadata.is_file() - { - let manifest_bytes = fs::read(&manifest_path).await?; - let manifest: ImageManifest = serde_json::from_slice(&manifest_bytes)?; - let config_bytes = fs::read(&config_path).await?; - let config: ImageConfiguration = serde_json::from_slice(&config_bytes)?; - debug!("cache hit digest={}", digest); - Ok(Some(OciPackedImage::new( - name, - digest.to_string(), - fs_path.clone(), - format, - descriptor, - OciSchema::new(config_bytes, config), - OciSchema::new(manifest_bytes, manifest), - ))) - } else { - Ok(None) - } - } else { - debug!("cache miss digest={}", digest); - Ok(None) - } - } - - pub async fn store(&self, packed: OciPackedImage) -> Result { - let mut index = self.index.write().await; - let mut manifests = index.manifests().clone(); - debug!("cache store digest={}", packed.digest); - let mut fs_path = self.cache_dir.clone(); - let mut manifest_path = self.cache_dir.clone(); - let mut config_path = self.cache_dir.clone(); - fs_path.push(format!("{}.{}", packed.digest, packed.format.extension())); - manifest_path.push(format!("{}.manifest.json", packed.digest)); - config_path.push(format!("{}.config.json", packed.digest)); - if fs::rename(&packed.path, &fs_path).await.is_err() { - fs::copy(&packed.path, &fs_path).await?; - fs::remove_file(&packed.path).await?; - } - fs::write(&config_path, packed.config.raw()).await?; - fs::write(&manifest_path, packed.manifest.raw()).await?; - manifests.retain(|item| { - if item.digest() != &packed.digest { - return true; - } - - let Some(format) = item - .annotations() - .as_ref() - .and_then(|x| x.get(ANNOTATION_OCI_PACKER_FORMAT)) - .map(|x| x.as_str()) - else { - return true; - }; - - if format != packed.format.extension() { - return true; - } - - false - }); - - let mut descriptor = packed.descriptor.clone(); - let mut annotations = descriptor.annotations().clone().unwrap_or_default(); - annotations.insert( - ANNOTATION_OCI_PACKER_FORMAT.to_string(), - packed.format.extension().to_string(), - ); - let image_name = packed.name.to_string(); - annotations.insert(ANNOTATION_IMAGE_NAME.to_string(), image_name); - let image_ref = packed.name.reference.clone(); - if let Some(image_ref) = image_ref { - annotations.insert(ANNOTATION_REF_NAME.to_string(), image_ref); - } - descriptor.set_annotations(Some(annotations)); - manifests.push(descriptor.clone()); - index.set_manifests(manifests); - self.save_index(&index).await?; - - let packed = OciPackedImage::new( - packed.name, - packed.digest, - fs_path.clone(), - packed.format, - descriptor, - packed.config, - packed.manifest, - ); - Ok(packed) - } - - async fn save_empty_index(&self) -> Result { - let index = ImageIndexBuilder::default() - .schema_version(2u32) - .media_type(MediaType::ImageIndex) - .manifests(Vec::new()) - .build()?; - self.save_index(&index).await?; - Ok(index) - } - - async fn load_index(&self) -> Result { - let mut index_path = self.cache_dir.clone(); - index_path.push("index.json"); - - if !index_path.exists() { - self.save_empty_index().await?; - } - - let content = fs::read_to_string(&index_path).await?; - let index = match serde_json::from_str::(&content) { - Ok(index) => index, - Err(error) => { - error!("image index was corrupted, creating a new one: {}", error); - self.save_empty_index().await? - } - }; - - Ok(index) - } - - async fn save_index(&self, index: &ImageIndex) -> Result<()> { - let mut encoded = serde_json::to_string_pretty(index)?; - encoded.push('\n'); - let mut index_path = self.cache_dir.clone(); - index_path.push("index.json"); - fs::write(&index_path, encoded).await?; - Ok(()) - } -} diff --git a/crates/oci/src/packer/mod.rs b/crates/oci/src/packer/mod.rs deleted file mode 100644 index 61b9fcad..00000000 --- a/crates/oci/src/packer/mod.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::path::PathBuf; - -use crate::{name::ImageName, schema::OciSchema}; - -use self::backend::OciPackerBackendType; -use oci_spec::image::{Descriptor, ImageConfiguration, ImageManifest}; - -pub mod backend; -pub mod cache; -pub mod service; - -#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)] -pub enum OciPackedFormat { - #[default] - Squashfs, - Erofs, - Tar, -} - -impl OciPackedFormat { - pub fn extension(&self) -> &str { - match self { - OciPackedFormat::Squashfs => "squashfs", - OciPackedFormat::Erofs => "erofs", - OciPackedFormat::Tar => "tar", - } - } - - pub fn backend(&self) -> OciPackerBackendType { - match self { - OciPackedFormat::Squashfs => OciPackerBackendType::MkSquashfs, - OciPackedFormat::Erofs => OciPackerBackendType::MkfsErofs, - OciPackedFormat::Tar => OciPackerBackendType::Tar, - } - } -} - -#[derive(Clone)] -pub struct OciPackedImage { - pub name: ImageName, - pub digest: String, - pub path: PathBuf, - pub format: OciPackedFormat, - pub descriptor: Descriptor, - pub config: OciSchema, - pub manifest: OciSchema, -} - -impl OciPackedImage { - pub fn new( - name: ImageName, - digest: String, - path: PathBuf, - format: OciPackedFormat, - descriptor: Descriptor, - config: OciSchema, - manifest: OciSchema, - ) -> OciPackedImage { - OciPackedImage { - name, - digest, - path, - format, - descriptor, - config, - manifest, - } - } -} diff --git a/crates/oci/src/packer/service.rs b/crates/oci/src/packer/service.rs deleted file mode 100644 index c5aebb10..00000000 --- a/crates/oci/src/packer/service.rs +++ /dev/null @@ -1,288 +0,0 @@ -use std::{ - collections::{hash_map::Entry, HashMap}, - fmt::Display, - path::{Path, PathBuf}, - sync::Arc, -}; - -use anyhow::{anyhow, Result}; -use oci_spec::image::Descriptor; -use tokio::{ - sync::{watch, Mutex}, - task::JoinHandle, -}; - -use crate::{ - assemble::OciImageAssembler, - fetch::{OciImageFetcher, OciResolvedImage}, - name::ImageName, - progress::{OciBoundProgress, OciProgress, OciProgressContext}, - registry::OciPlatform, -}; - -use log::{error, info, warn}; - -use super::{cache::OciPackerCache, OciPackedFormat, OciPackedImage}; - -pub struct OciPackerTask { - progress: OciBoundProgress, - watch: watch::Sender>>, - task: JoinHandle<()>, -} - -#[derive(Clone)] -pub struct OciPackerService { - seed: Option, - platform: OciPlatform, - cache: OciPackerCache, - tasks: Arc>>, -} - -impl OciPackerService { - pub async fn new( - seed: Option, - cache_dir: &Path, - platform: OciPlatform, - ) -> Result { - Ok(OciPackerService { - seed, - cache: OciPackerCache::new(cache_dir).await?, - platform, - tasks: Arc::new(Mutex::new(HashMap::new())), - }) - } - - pub async fn list(&self) -> Result> { - self.cache.list().await - } - - pub async fn recall( - &self, - digest: &str, - format: OciPackedFormat, - ) -> Result> { - if digest.contains('/') || digest.contains('\\') || digest.contains("..") { - return Ok(None); - } - - self.cache - .recall(ImageName::parse("cached:latest")?, digest, format) - .await - } - - pub async fn request( - &self, - name: ImageName, - format: OciPackedFormat, - overwrite: bool, - pull: bool, - progress_context: OciProgressContext, - ) -> Result { - let progress = OciProgress::new(); - let progress = OciBoundProgress::new(progress_context.clone(), progress); - let mut resolved = None; - if !pull && !overwrite { - resolved = self.cache.resolve(name.clone(), format).await?; - } - let fetcher = - OciImageFetcher::new(self.seed.clone(), self.platform.clone(), progress.clone()); - let resolved = if let Some(resolved) = resolved { - resolved - } else { - fetcher.resolve(name.clone()).await? - }; - - let key = OciPackerTaskKey { - digest: resolved.digest.clone(), - format, - }; - let (progress_copy_task, mut receiver) = match self.tasks.lock().await.entry(key.clone()) { - Entry::Occupied(entry) => { - let entry = entry.get(); - ( - Some(entry.progress.also_update(progress_context).await), - entry.watch.subscribe(), - ) - } - - Entry::Vacant(entry) => { - let task = self - .clone() - .launch( - name, - key.clone(), - format, - overwrite, - resolved, - fetcher, - progress.clone(), - ) - .await; - let (watch, receiver) = watch::channel(None); - - let task = OciPackerTask { - progress: progress.clone(), - task, - watch, - }; - entry.insert(task); - (None, receiver) - } - }; - - let _progress_task_guard = scopeguard::guard(progress_copy_task, |task| { - if let Some(task) = task { - task.abort(); - } - }); - - let _task_cancel_guard = scopeguard::guard(self.clone(), |service| { - service.maybe_cancel_task(key); - }); - - loop { - receiver.changed().await?; - let current = receiver.borrow_and_update(); - if current.is_some() { - return current - .as_ref() - .map(|x| x.as_ref().map_err(|err| anyhow!("{}", err)).cloned()) - .unwrap(); - } - } - } - - #[allow(clippy::too_many_arguments)] - async fn launch( - self, - name: ImageName, - key: OciPackerTaskKey, - format: OciPackedFormat, - overwrite: bool, - resolved: OciResolvedImage, - fetcher: OciImageFetcher, - progress: OciBoundProgress, - ) -> JoinHandle<()> { - info!("started packer task {}", key); - tokio::task::spawn(async move { - let _task_drop_guard = - scopeguard::guard((key.clone(), self.clone()), |(key, service)| { - service.ensure_task_gone(key); - }); - if let Err(error) = self - .task( - name, - key.clone(), - format, - overwrite, - resolved, - fetcher, - progress, - ) - .await - { - self.finish(&key, Err(error)).await; - } - }) - } - - #[allow(clippy::too_many_arguments)] - async fn task( - &self, - name: ImageName, - key: OciPackerTaskKey, - format: OciPackedFormat, - overwrite: bool, - resolved: OciResolvedImage, - fetcher: OciImageFetcher, - progress: OciBoundProgress, - ) -> Result<()> { - if !overwrite { - if let Some(cached) = self - .cache - .recall(name.clone(), &resolved.digest, format) - .await? - { - self.finish(&key, Ok(cached)).await; - return Ok(()); - } - } - let assembler = - OciImageAssembler::new(fetcher, resolved, progress.clone(), None, None).await?; - let assembled = assembler.assemble().await?; - let mut file = assembled - .tmp_dir - .clone() - .ok_or(anyhow!("tmp_dir was missing when packing image"))?; - file.push("image.pack"); - let target = file.clone(); - let packer = format.backend().create(); - packer - .pack(progress, assembled.vfs.clone(), &target) - .await?; - let packed = OciPackedImage::new( - name, - assembled.digest.clone(), - file, - format, - assembled.descriptor.clone(), - assembled.config.clone(), - assembled.manifest.clone(), - ); - let packed = self.cache.store(packed).await?; - self.finish(&key, Ok(packed)).await; - Ok(()) - } - - async fn finish(&self, key: &OciPackerTaskKey, result: Result) { - let Some(task) = self.tasks.lock().await.remove(key) else { - error!("packer task {} was not found when task completed", key); - return; - }; - - match result.as_ref() { - Ok(_) => { - info!("completed packer task {}", key); - } - - Err(err) => { - warn!("packer task {} failed: {}", key, err); - } - } - - task.watch.send_replace(Some(result)); - } - - fn maybe_cancel_task(self, key: OciPackerTaskKey) { - tokio::task::spawn(async move { - let tasks = self.tasks.lock().await; - if let Some(task) = tasks.get(&key) { - if task.watch.is_closed() { - task.task.abort(); - } - } - }); - } - - fn ensure_task_gone(self, key: OciPackerTaskKey) { - tokio::task::spawn(async move { - let mut tasks = self.tasks.lock().await; - if let Some(task) = tasks.remove(&key) { - warn!("aborted packer task {}", key); - task.watch.send_replace(Some(Err(anyhow!("task aborted")))); - } - }); - } -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -struct OciPackerTaskKey { - digest: String, - format: OciPackedFormat, -} - -impl Display for OciPackerTaskKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{}:{}", self.digest, self.format.extension())) - } -} diff --git a/crates/oci/src/progress.rs b/crates/oci/src/progress.rs deleted file mode 100644 index 42b975e1..00000000 --- a/crates/oci/src/progress.rs +++ /dev/null @@ -1,238 +0,0 @@ -use indexmap::IndexMap; -use std::sync::Arc; -use tokio::{ - sync::{watch, Mutex}, - task::JoinHandle, -}; - -#[derive(Clone, Debug)] -pub struct OciProgress { - pub phase: OciProgressPhase, - pub digest: Option, - pub layers: IndexMap, - pub indication: OciProgressIndication, -} - -impl Default for OciProgress { - fn default() -> Self { - Self::new() - } -} - -impl OciProgress { - pub fn new() -> Self { - OciProgress { - phase: OciProgressPhase::Started, - digest: None, - layers: IndexMap::new(), - indication: OciProgressIndication::Hidden, - } - } - - pub fn start_resolving(&mut self) { - self.phase = OciProgressPhase::Resolving; - self.indication = OciProgressIndication::Spinner { message: None }; - } - - pub fn resolved(&mut self, digest: &str) { - self.digest = Some(digest.to_string()); - self.indication = OciProgressIndication::Hidden; - } - - pub fn add_layer(&mut self, id: &str) { - self.layers.insert( - id.to_string(), - OciProgressLayer { - id: id.to_string(), - phase: OciProgressLayerPhase::Waiting, - indication: OciProgressIndication::Spinner { message: None }, - }, - ); - } - - pub fn downloading_layer(&mut self, id: &str, downloaded: u64, total: u64) { - if let Some(entry) = self.layers.get_mut(id) { - entry.phase = OciProgressLayerPhase::Downloading; - entry.indication = OciProgressIndication::ProgressBar { - message: None, - current: downloaded, - total, - bytes: true, - }; - } - } - - pub fn downloaded_layer(&mut self, id: &str, total: u64) { - if let Some(entry) = self.layers.get_mut(id) { - entry.phase = OciProgressLayerPhase::Downloaded; - entry.indication = OciProgressIndication::Completed { - message: None, - total: Some(total), - bytes: true, - }; - } - } - - pub fn start_assemble(&mut self) { - self.phase = OciProgressPhase::Assemble; - self.indication = OciProgressIndication::Hidden; - } - - pub fn start_extracting_layer(&mut self, id: &str) { - if let Some(entry) = self.layers.get_mut(id) { - entry.phase = OciProgressLayerPhase::Extracting; - entry.indication = OciProgressIndication::Spinner { message: None }; - } - } - - pub fn extracting_layer(&mut self, id: &str, file: &str) { - if let Some(entry) = self.layers.get_mut(id) { - entry.phase = OciProgressLayerPhase::Extracting; - entry.indication = OciProgressIndication::Spinner { - message: Some(file.to_string()), - }; - } - } - - pub fn extracted_layer(&mut self, id: &str, count: u64, total_size: u64) { - if let Some(entry) = self.layers.get_mut(id) { - entry.phase = OciProgressLayerPhase::Extracted; - entry.indication = OciProgressIndication::Completed { - message: Some(format!("{} files", count)), - total: Some(total_size), - bytes: true, - }; - } - } - - pub fn start_packing(&mut self) { - self.phase = OciProgressPhase::Pack; - for layer in self.layers.values_mut() { - layer.indication = OciProgressIndication::Hidden; - } - self.indication = OciProgressIndication::Spinner { message: None }; - } - - pub fn complete(&mut self, size: u64) { - self.phase = OciProgressPhase::Complete; - self.indication = OciProgressIndication::Completed { - message: None, - total: Some(size), - bytes: true, - } - } -} - -#[derive(Clone, Debug)] -pub enum OciProgressPhase { - Started, - Resolving, - Resolved, - ConfigDownload, - LayerDownload, - Assemble, - Pack, - Complete, -} - -#[derive(Clone, Debug)] -pub enum OciProgressIndication { - Hidden, - - ProgressBar { - message: Option, - current: u64, - total: u64, - bytes: bool, - }, - - Spinner { - message: Option, - }, - - Completed { - message: Option, - total: Option, - bytes: bool, - }, -} - -#[derive(Clone, Debug)] -pub struct OciProgressLayer { - pub id: String, - pub phase: OciProgressLayerPhase, - pub indication: OciProgressIndication, -} - -#[derive(Clone, Debug)] -pub enum OciProgressLayerPhase { - Waiting, - Downloading, - Downloaded, - Extracting, - Extracted, -} - -#[derive(Clone)] -pub struct OciProgressContext { - sender: watch::Sender, -} - -impl OciProgressContext { - pub fn create() -> (OciProgressContext, watch::Receiver) { - let (sender, receiver) = watch::channel(OciProgress::new()); - (OciProgressContext::new(sender), receiver) - } - - pub fn new(sender: watch::Sender) -> OciProgressContext { - OciProgressContext { sender } - } - - pub fn update(&self, progress: &OciProgress) { - let _ = self.sender.send(progress.clone()); - } - - pub fn subscribe(&self) -> watch::Receiver { - self.sender.subscribe() - } -} - -#[derive(Clone)] -pub struct OciBoundProgress { - context: OciProgressContext, - instance: Arc>, -} - -impl OciBoundProgress { - pub fn new(context: OciProgressContext, progress: OciProgress) -> OciBoundProgress { - OciBoundProgress { - context, - instance: Arc::new(Mutex::new(progress)), - } - } - - pub async fn update(&self, function: impl FnOnce(&mut OciProgress)) { - let mut progress = self.instance.lock().await; - function(&mut progress); - self.context.update(&progress); - } - - pub fn update_blocking(&self, function: impl FnOnce(&mut OciProgress)) { - let mut progress = self.instance.blocking_lock(); - function(&mut progress); - self.context.update(&progress); - } - - pub async fn also_update(&self, context: OciProgressContext) -> JoinHandle<()> { - let progress = self.instance.lock().await.clone(); - context.update(&progress); - let mut receiver = self.context.subscribe(); - tokio::task::spawn(async move { - while receiver.changed().await.is_ok() { - context - .sender - .send_replace(receiver.borrow_and_update().clone()); - } - }) - } -} diff --git a/crates/oci/src/registry.rs b/crates/oci/src/registry.rs deleted file mode 100644 index 4e4a8570..00000000 --- a/crates/oci/src/registry.rs +++ /dev/null @@ -1,270 +0,0 @@ -use std::collections::HashMap; - -use anyhow::{anyhow, Result}; -use bytes::Bytes; -use oci_spec::image::{Arch, Descriptor, ImageIndex, ImageManifest, MediaType, Os, ToDockerV2S2}; -use reqwest::{Client, RequestBuilder, Response, StatusCode}; -use tokio::{fs::File, io::AsyncWriteExt}; -use url::Url; - -use crate::{name::ImageName, progress::OciBoundProgress, schema::OciSchema}; - -#[derive(Clone, Debug)] -pub struct OciPlatform { - pub os: Os, - pub arch: Arch, -} - -impl OciPlatform { - #[cfg(target_arch = "x86_64")] - const CURRENT_ARCH: Arch = Arch::Amd64; - #[cfg(target_arch = "aarch64")] - const CURRENT_ARCH: Arch = Arch::ARM64; - - pub fn new(os: Os, arch: Arch) -> OciPlatform { - OciPlatform { os, arch } - } - - pub fn current() -> OciPlatform { - OciPlatform { - os: Os::Linux, - arch: OciPlatform::CURRENT_ARCH, - } - } -} - -pub struct OciRegistryClient { - agent: Client, - url: Url, - platform: OciPlatform, - token: Option, -} - -impl OciRegistryClient { - pub fn new(url: Url, platform: OciPlatform) -> Result { - Ok(OciRegistryClient { - agent: Client::new(), - url, - platform, - token: None, - }) - } - - async fn call(&mut self, mut req: RequestBuilder) -> Result { - if let Some(ref token) = self.token { - req = req.bearer_auth(token); - } - let req_first_try = req.try_clone().ok_or(anyhow!("request is not clonable"))?; - let response = self.agent.execute(req_first_try.build()?).await?; - if response.status() == StatusCode::UNAUTHORIZED && self.token.is_none() { - let Some(www_authenticate) = response.headers().get("www-authenticate") else { - return Err(anyhow!("not authorized to perform this action")); - }; - - let www_authenticate = www_authenticate.to_str()?; - if !www_authenticate.starts_with("Bearer ") { - return Err(anyhow!("unknown authentication scheme")); - } - - let details = &www_authenticate[7..]; - let details = details - .split(',') - .map(|x| x.split('=')) - .map(|mut x| (x.next(), x.next())) - .filter(|(key, value)| key.is_some() && value.is_some()) - .map(|(key, value)| { - ( - key.unwrap().trim().to_lowercase(), - value.unwrap().trim().to_string(), - ) - }) - .map(|(key, value)| (key, value.trim_matches('\"').to_string())) - .collect::>(); - let realm = details.get("realm"); - let service = details.get("service"); - let scope = details.get("scope"); - if realm.is_none() || service.is_none() || scope.is_none() { - return Err(anyhow!( - "unknown authentication scheme: realm, service, and scope are required" - )); - } - let mut url = Url::parse(realm.unwrap())?; - url.query_pairs_mut() - .append_pair("service", service.unwrap()) - .append_pair("scope", scope.unwrap()); - let token_response = self.agent.get(url.clone()).send().await?; - if token_response.status() != StatusCode::OK { - return Err(anyhow!( - "failed to acquire token via {}: status {}", - url, - token_response.status() - )); - } - let token_bytes = token_response.bytes().await?; - let token = serde_json::from_slice::(&token_bytes)?; - let token = token - .get("token") - .and_then(|x| x.as_str()) - .ok_or(anyhow!("token key missing from response"))?; - self.token = Some(token.to_string()); - return Ok(self.agent.execute(req.bearer_auth(token).build()?).await?); - } - - if !response.status().is_success() { - return Err(anyhow!( - "request to {} failed: status {}", - req.build()?.url(), - response.status() - )); - } - - Ok(response) - } - - pub async fn get_blob>( - &mut self, - name: N, - descriptor: &Descriptor, - ) -> Result { - let url = self.url.join(&format!( - "/v2/{}/blobs/{}", - name.as_ref(), - descriptor.digest() - ))?; - let response = self.call(self.agent.get(url.as_str())).await?; - Ok(response.bytes().await?) - } - - pub async fn write_blob_to_file>( - &mut self, - name: N, - descriptor: &Descriptor, - mut dest: File, - progress: Option, - ) -> Result { - let url = self.url.join(&format!( - "/v2/{}/blobs/{}", - name.as_ref(), - descriptor.digest() - ))?; - let mut response = self.call(self.agent.get(url.as_str())).await?; - let mut size: u64 = 0; - while let Some(chunk) = response.chunk().await? { - dest.write_all(&chunk).await?; - size += chunk.len() as u64; - - if let Some(ref progress) = progress { - progress - .update(|progress| { - progress.downloading_layer( - descriptor.digest(), - size, - descriptor.size() as u64, - ); - }) - .await; - } - } - Ok(size) - } - - async fn get_raw_manifest_with_digest, R: AsRef>( - &mut self, - name: N, - reference: R, - ) -> Result<(OciSchema, String)> { - let url = self.url.join(&format!( - "/v2/{}/manifests/{}", - name.as_ref(), - reference.as_ref(), - ))?; - let accept = format!( - "{}, {}, {}, {}", - MediaType::ImageManifest.to_docker_v2s2()?, - MediaType::ImageManifest, - MediaType::ImageIndex, - MediaType::ImageIndex.to_docker_v2s2()?, - ); - let response = self - .call(self.agent.get(url.as_str()).header("Accept", &accept)) - .await?; - let digest = response - .headers() - .get("Docker-Content-Digest") - .ok_or_else(|| anyhow!("fetching manifest did not yield a content digest"))? - .to_str()? - .to_string(); - let bytes = response.bytes().await?; - let manifest = serde_json::from_slice(&bytes)?; - Ok((OciSchema::new(bytes.to_vec(), manifest), digest)) - } - - pub async fn get_manifest_with_digest, R: AsRef>( - &mut self, - name: N, - reference: Option, - digest: Option, - ) -> Result<(OciSchema, Option, String)> { - let what = digest - .as_ref() - .map(|x| x.as_ref().to_string()) - .unwrap_or_else(|| { - reference - .map(|x| x.as_ref().to_string()) - .unwrap_or_else(|| ImageName::DEFAULT_IMAGE_TAG.to_string()) - }); - let url = self - .url - .join(&format!("/v2/{}/manifests/{}", name.as_ref(), what,))?; - let accept = format!( - "{}, {}, {}, {}", - MediaType::ImageManifest.to_docker_v2s2()?, - MediaType::ImageManifest, - MediaType::ImageIndex, - MediaType::ImageIndex.to_docker_v2s2()?, - ); - let response = self - .call(self.agent.get(url.as_str()).header("Accept", &accept)) - .await?; - let content_type = response - .headers() - .get("Content-Type") - .ok_or_else(|| anyhow!("registry response did not have a Content-Type header"))? - .to_str()?; - if content_type == MediaType::ImageIndex.to_string() - || content_type == MediaType::ImageIndex.to_docker_v2s2()? - { - let index = serde_json::from_str(&response.text().await?)?; - let descriptor = self - .pick_manifest(index) - .ok_or_else(|| anyhow!("unable to pick manifest from index"))?; - let (manifest, digest) = self - .get_raw_manifest_with_digest(name, descriptor.digest()) - .await?; - return Ok((manifest, Some(descriptor), digest)); - } - let digest = response - .headers() - .get("Docker-Content-Digest") - .and_then(|x| x.to_str().ok()) - .map(|x| x.to_string()) - .or_else(|| digest.map(|x: N| x.as_ref().to_string())) - .ok_or_else(|| anyhow!("fetching manifest did not yield a content digest"))?; - let bytes = response.bytes().await?; - let manifest = serde_json::from_slice(&bytes)?; - Ok((OciSchema::new(bytes.to_vec(), manifest), None, digest)) - } - - fn pick_manifest(&mut self, index: ImageIndex) -> Option { - for item in index.manifests() { - if let Some(platform) = item.platform() { - if *platform.os() == self.platform.os - && *platform.architecture() == self.platform.arch - { - return Some(item.clone()); - } - } - } - None - } -} diff --git a/crates/oci/src/schema.rs b/crates/oci/src/schema.rs deleted file mode 100644 index 04bc2030..00000000 --- a/crates/oci/src/schema.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::fmt::Debug; - -#[derive(Clone, Debug)] -pub struct OciSchema { - raw: Vec, - item: T, -} - -impl OciSchema { - pub fn new(raw: Vec, item: T) -> OciSchema { - OciSchema { raw, item } - } - - pub fn raw(&self) -> &[u8] { - &self.raw - } - - pub fn item(&self) -> &T { - &self.item - } - - pub fn into_raw(self) -> Vec { - self.raw - } - - pub fn into_item(self) -> T { - self.item - } -} diff --git a/crates/oci/src/vfs.rs b/crates/oci/src/vfs.rs deleted file mode 100644 index 8a464d85..00000000 --- a/crates/oci/src/vfs.rs +++ /dev/null @@ -1,264 +0,0 @@ -use std::path::{Path, PathBuf}; - -use anyhow::{anyhow, Result}; -use tokio::{ - fs::File, - io::{AsyncRead, AsyncWrite, AsyncWriteExt}, -}; -use tokio_tar::{Builder, Entry, EntryType, Header}; - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum VfsNodeType { - Directory, - RegularFile, - Symlink, - Hardlink, - Fifo, - CharDevice, - BlockDevice, -} - -#[derive(Clone, Debug)] -pub struct VfsNode { - pub name: String, - pub size: u64, - pub children: Vec, - pub typ: VfsNodeType, - pub uid: u64, - pub gid: u64, - pub link_name: Option, - pub mode: u32, - pub mtime: u64, - pub dev_major: Option, - pub dev_minor: Option, - pub disk_path: Option, -} - -impl VfsNode { - pub fn from(entry: &Entry) -> Result { - let header = entry.header(); - let name = entry - .path()? - .file_name() - .ok_or(anyhow!("unable to get file name for entry"))? - .to_string_lossy() - .to_string(); - let typ = header.entry_type(); - let vtype = if typ.is_symlink() { - VfsNodeType::Symlink - } else if typ.is_hard_link() { - VfsNodeType::Hardlink - } else if typ.is_dir() { - VfsNodeType::Directory - } else if typ.is_fifo() { - VfsNodeType::Fifo - } else if typ.is_block_special() { - VfsNodeType::BlockDevice - } else if typ.is_character_special() { - VfsNodeType::CharDevice - } else if typ.is_file() { - VfsNodeType::RegularFile - } else { - return Err(anyhow!("unable to determine vfs type for entry")); - }; - - Ok(VfsNode { - name, - size: header.size()?, - children: vec![], - typ: vtype, - uid: header.uid()?, - gid: header.gid()?, - link_name: header.link_name()?.map(|x| x.to_string_lossy().to_string()), - mode: header.mode()?, - mtime: header.mtime()?, - dev_major: header.device_major()?, - dev_minor: header.device_minor()?, - disk_path: None, - }) - } - - pub fn lookup(&self, path: &Path) -> Option<&VfsNode> { - let mut node = self; - for part in path { - node = node - .children - .iter() - .find(|child| child.name == part.to_string_lossy())?; - } - Some(node) - } - - pub fn lookup_mut(&mut self, path: &Path) -> Option<&mut VfsNode> { - let mut node = self; - for part in path { - node = node - .children - .iter_mut() - .find(|child| child.name == part.to_string_lossy())?; - } - Some(node) - } - - pub fn remove(&mut self, path: &Path) -> Option<(&mut VfsNode, VfsNode)> { - let parent = path.parent()?; - let node = self.lookup_mut(parent)?; - let file_name = path.file_name()?; - let file_name = file_name.to_string_lossy(); - let position = node - .children - .iter() - .position(|child| file_name == child.name)?; - let removed = node.children.remove(position); - Some((node, removed)) - } - - pub fn create_tar_header(&self) -> Result
{ - let mut header = Header::new_ustar(); - header.set_entry_type(match self.typ { - VfsNodeType::Directory => EntryType::Directory, - VfsNodeType::CharDevice => EntryType::Char, - VfsNodeType::BlockDevice => EntryType::Block, - VfsNodeType::Fifo => EntryType::Fifo, - VfsNodeType::Hardlink => EntryType::Link, - VfsNodeType::Symlink => EntryType::Symlink, - VfsNodeType::RegularFile => EntryType::Regular, - }); - header.set_uid(self.uid); - header.set_gid(self.gid); - - if let Some(device_major) = self.dev_major { - header.set_device_major(device_major)?; - } - - if let Some(device_minor) = self.dev_minor { - header.set_device_minor(device_minor)?; - } - header.set_mtime(self.mtime); - header.set_mode(self.mode); - - if let Some(link_name) = self.link_name.as_ref() { - header.set_link_name(PathBuf::from(link_name))?; - } - header.set_size(self.size); - Ok(header) - } - - pub async fn write_to_tar( - &self, - path: &Path, - builder: &mut Builder, - ) -> Result<()> { - let mut header = self.create_tar_header()?; - header.set_path(path)?; - header.set_cksum(); - if let Some(disk_path) = self.disk_path.as_ref() { - builder - .append(&header, File::open(disk_path).await?) - .await?; - } else { - builder.append(&header, &[] as &[u8]).await?; - } - Ok(()) - } -} - -#[derive(Clone, Debug)] -pub struct VfsTree { - pub root: VfsNode, -} - -impl Default for VfsTree { - fn default() -> Self { - Self::new() - } -} - -impl VfsTree { - pub fn new() -> VfsTree { - VfsTree { - root: VfsNode { - name: "".to_string(), - size: 0, - children: vec![], - typ: VfsNodeType::Directory, - uid: 0, - gid: 0, - link_name: None, - mode: 0, - mtime: 0, - dev_major: None, - dev_minor: None, - disk_path: None, - }, - } - } - - pub fn insert_tar_entry(&mut self, entry: &Entry) -> Result<&VfsNode> { - let mut meta = VfsNode::from(entry)?; - let path = entry.path()?.to_path_buf(); - let parent = if let Some(parent) = path.parent() { - self.root.lookup_mut(parent) - } else { - Some(&mut self.root) - }; - - let Some(parent) = parent else { - return Err(anyhow!("unable to find parent of entry")); - }; - - let position = parent - .children - .iter() - .position(|child| meta.name == child.name); - - if let Some(position) = position { - let old = parent.children.remove(position); - if meta.typ == VfsNodeType::Directory { - meta.children = old.children; - } - } - parent.children.push(meta.clone()); - let Some(reference) = parent.children.iter().find(|child| child.name == meta.name) else { - return Err(anyhow!("unable to find inserted child in vfs")); - }; - Ok(reference) - } - - pub fn set_disk_path(&mut self, path: &Path, disk_path: &Path) -> Result<()> { - let Some(node) = self.root.lookup_mut(path) else { - return Err(anyhow!( - "unable to find node {:?} to set disk path to", - path - )); - }; - node.disk_path = Some(disk_path.to_path_buf()); - Ok(()) - } - - pub async fn write_to_tar( - &self, - write: W, - ) -> Result<()> { - let mut builder = Builder::new(write); - let mut queue = vec![(PathBuf::from(""), &self.root)]; - - while !queue.is_empty() { - let (mut path, node) = queue.remove(0); - if !node.name.is_empty() { - path.push(&node.name); - } - if path.components().count() != 0 { - node.write_to_tar(&path, &mut builder).await?; - } - for child in &node.children { - queue.push((path.clone(), child)); - } - } - - let mut write = builder.into_inner().await?; - write.flush().await?; - drop(write); - Ok(()) - } -} diff --git a/crates/runtime/Cargo.toml b/crates/runtime/Cargo.toml deleted file mode 100644 index cbfd8730..00000000 --- a/crates/runtime/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "krata-runtime" -description = "Runtime for managing zones on the krata isolation engine" -license.workspace = true -version.workspace = true -homepage.workspace = true -repository.workspace = true -edition = "2021" -resolver = "2" - -[dependencies] -anyhow = { workspace = true } -backhand = { workspace = true } -ipnetwork = { workspace = true } -krata = { path = "../krata", version = "^0.0.21" } -krata-advmac = { workspace = true } -krata-oci = { path = "../oci", version = "^0.0.21" } -log = { workspace = true } -serde_json = { workspace = true } -tokio = { workspace = true } -uuid = { workspace = true } -krata-loopdev = { path = "../loopdev", version = "^0.0.21" } -krata-xencall = { path = "../xen/xencall", version = "^0.0.21" } -krata-xenclient = { path = "../xen/xenclient", version = "^0.0.21" } -krata-xenevtchn = { path = "../xen/xenevtchn", version = "^0.0.21" } -krata-xengnt = { path = "../xen/xengnt", version = "^0.0.21" } -krata-xenplatform = { path = "../xen/xenplatform", version = "^0.0.21" } -krata-xenstore = { path = "../xen/xenstore", version = "^0.0.21" } -walkdir = { workspace = true } -indexmap = { workspace = true } - -[lib] -name = "kratart" - -[dev-dependencies] -env_logger = { workspace = true } - -[[example]] -name = "kratart-channel" -path = "examples/channel.rs" diff --git a/crates/runtime/examples/channel.rs b/crates/runtime/examples/channel.rs deleted file mode 100644 index 9e81e1ff..00000000 --- a/crates/runtime/examples/channel.rs +++ /dev/null @@ -1,23 +0,0 @@ -use anyhow::Result; -use env_logger::Env; -use kratart::channel::ChannelService; - -#[tokio::main] -async fn main() -> Result<()> { - env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); - - let (service, _, mut receiver) = ChannelService::new("krata-channel".to_string(), None).await?; - let task = service.launch().await?; - - loop { - let Some((id, data)) = receiver.recv().await else { - break; - }; - - println!("domain {} = {:?}", id, data); - } - - task.abort(); - - Ok(()) -} diff --git a/crates/runtime/src/autoloop.rs b/crates/runtime/src/autoloop.rs deleted file mode 100644 index c12b360f..00000000 --- a/crates/runtime/src/autoloop.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::{sync::Arc, time::Duration}; - -use anyhow::{anyhow, Result}; -use krataloopdev::{LoopControl, LoopDevice}; -use log::debug; -use tokio::time::sleep; -use xenclient::BlockDeviceRef; - -#[derive(Clone)] -pub struct AutoLoop { - control: Arc, -} - -impl AutoLoop { - pub fn new(control: LoopControl) -> AutoLoop { - AutoLoop { - control: Arc::new(control), - } - } - - pub fn loopify(&self, file: &str) -> Result { - debug!("creating loop for file {}", file); - let device = self.control.next_free()?; - device.with().read_only(true).attach(file)?; - let path = device - .path() - .ok_or(anyhow!("unable to get loop device path"))? - .to_str() - .ok_or(anyhow!("unable to convert loop device path to string",))? - .to_string(); - let major = device.major()?; - let minor = device.minor()?; - Ok(BlockDeviceRef { path, major, minor }) - } - - pub async fn unloop(&self, device: &str) -> Result<()> { - let device = LoopDevice::open(device)?; - device.detach()?; - sleep(Duration::from_millis(200)).await; - Ok(()) - } -} diff --git a/crates/runtime/src/cfgblk.rs b/crates/runtime/src/cfgblk.rs deleted file mode 100644 index 01cf97c6..00000000 --- a/crates/runtime/src/cfgblk.rs +++ /dev/null @@ -1,69 +0,0 @@ -use anyhow::Result; -use backhand::compression::Compressor; -use backhand::{FilesystemCompressor, FilesystemWriter, NodeHeader}; -use krata::launchcfg::LaunchInfo; -use krataoci::packer::OciPackedImage; -use log::trace; -use std::fs; -use std::fs::File; -use std::path::PathBuf; -use uuid::Uuid; - -pub struct ConfigBlock { - pub image: OciPackedImage, - pub file: PathBuf, - pub dir: PathBuf, -} - -impl ConfigBlock { - pub fn new(uuid: &Uuid, image: OciPackedImage) -> Result { - let mut dir = std::env::temp_dir().clone(); - dir.push(format!("krata-cfg-{}", uuid)); - fs::create_dir_all(&dir)?; - let mut file = dir.clone(); - file.push("config.squashfs"); - Ok(ConfigBlock { image, file, dir }) - } - - pub fn build(&self, launch_config: &LaunchInfo) -> Result<()> { - trace!("build launch_config={:?}", launch_config); - let config = self.image.config.raw(); - let launch = serde_json::to_string(launch_config)?; - let mut writer = FilesystemWriter::default(); - writer.set_compressor(FilesystemCompressor::new(Compressor::Gzip, None)?); - writer.push_dir( - "/image", - NodeHeader { - permissions: 384, - uid: 0, - gid: 0, - mtime: 0, - }, - )?; - writer.push_file( - config, - "/image/config.json", - NodeHeader { - permissions: 384, - uid: 0, - gid: 0, - mtime: 0, - }, - )?; - writer.push_file( - launch.as_bytes(), - "/launch.json", - NodeHeader { - permissions: 384, - uid: 0, - gid: 0, - mtime: 0, - }, - )?; - let mut file = File::create(&self.file)?; - trace!("build write sqaushfs"); - writer.write(&mut file)?; - trace!("build complete"); - Ok(()) - } -} diff --git a/crates/runtime/src/channel.rs b/crates/runtime/src/channel.rs deleted file mode 100644 index 3ecbb2be..00000000 --- a/crates/runtime/src/channel.rs +++ /dev/null @@ -1,537 +0,0 @@ -use std::{ - collections::HashMap, - sync::atomic::{fence, Ordering}, - time::Duration, -}; - -use anyhow::{anyhow, Result}; -use log::{debug, error}; -use tokio::{ - select, - sync::mpsc::{channel, Receiver, Sender}, - task::JoinHandle, - time::sleep, -}; -use xenevtchn::EventChannelService; -use xengnt::{sys::GrantRef, GrantTab, MappedMemory}; -use xenstore::{XsdClient, XsdInterface}; - -const SINGLE_CHANNEL_QUEUE_LEN: usize = 100; -const GROUPED_CHANNEL_QUEUE_LEN: usize = 1000; - -#[repr(C)] -struct XenConsoleInterface { - input: [u8; XenConsoleInterface::INPUT_SIZE], - output: [u8; XenConsoleInterface::OUTPUT_SIZE], - in_cons: u32, - in_prod: u32, - out_cons: u32, - out_prod: u32, -} - -unsafe impl Send for XenConsoleInterface {} - -impl XenConsoleInterface { - const INPUT_SIZE: usize = 1024; - const OUTPUT_SIZE: usize = 2048; -} - -pub struct ChannelService { - typ: String, - use_reserved_ref: Option, - backends: HashMap, - evtchn: EventChannelService, - store: XsdClient, - gnttab: GrantTab, - input_receiver: Receiver<(u32, Vec)>, - pub input_sender: Sender<(u32, Vec)>, - output_sender: Sender<(u32, Option>)>, -} - -impl ChannelService { - pub async fn new( - typ: String, - use_reserved_ref: Option, - ) -> Result<( - ChannelService, - Sender<(u32, Vec)>, - Receiver<(u32, Option>)>, - )> { - let (input_sender, input_receiver) = channel(GROUPED_CHANNEL_QUEUE_LEN); - let (output_sender, output_receiver) = channel(GROUPED_CHANNEL_QUEUE_LEN); - - debug!("opening xenevtchn"); - let evtchn = EventChannelService::open().await?; - debug!("opening xenstore"); - let store = XsdClient::open().await?; - debug!("opening xengnt"); - let gnttab = GrantTab::open()?; - - Ok(( - ChannelService { - typ, - use_reserved_ref, - backends: HashMap::new(), - evtchn, - store, - gnttab, - input_sender: input_sender.clone(), - input_receiver, - output_sender, - }, - input_sender, - output_receiver, - )) - } - - pub async fn launch(mut self) -> Result> { - Ok(tokio::task::spawn(async move { - if let Err(error) = self.process().await { - error!("channel processor failed: {}", error); - } - })) - } - - async fn process(&mut self) -> Result<()> { - self.scan_all_backends().await?; - let mut watch_handle = self - .store - .create_watch("/local/domain/0/backend/console") - .await?; - self.store.bind_watch(&watch_handle).await?; - loop { - select! { - x = watch_handle.receiver.recv() => match x { - Some(_) => { - self.scan_all_backends().await?; - } - - None => { - break; - } - }, - - x = self.input_receiver.recv() => match x { - Some((domid, data)) => { - if let Some(backend) = self.backends.get_mut(&domid) { - let _ = backend.sender.try_send(data); - } - }, - - None => { - break; - } - } - } - } - Ok(()) - } - - pub async fn send(&mut self, domid: u32, message: Vec) -> Result<()> { - if let Some(backend) = self.backends.get(&domid) { - backend.sender.send(message).await?; - } - Ok(()) - } - - async fn ensure_backend_exists(&mut self, domid: u32, id: u32, path: String) -> Result<()> { - if self.backends.contains_key(&domid) { - return Ok(()); - } - let Some(frontend_path) = self.store.read_string(format!("{}/frontend", path)).await? - else { - return Ok(()); - }; - let Some(typ) = self - .store - .read_string(format!("{}/type", frontend_path)) - .await? - else { - return Ok(()); - }; - - if typ != self.typ { - return Ok(()); - } - - let backend = ChannelBackend::new( - path.clone(), - frontend_path.clone(), - domid, - id, - self.store.clone(), - self.evtchn.clone(), - self.gnttab.clone(), - self.output_sender.clone(), - self.use_reserved_ref, - ) - .await?; - self.backends.insert(domid, backend); - Ok(()) - } - - async fn scan_all_backends(&mut self) -> Result<()> { - let domains = self.store.list("/local/domain/0/backend/console").await?; - let mut seen: Vec = Vec::new(); - for domid_string in &domains { - let domid = domid_string.parse::()?; - let domid_path = format!("/local/domain/0/backend/console/{}", domid); - for id_string in self.store.list(&domid_path).await? { - let id = id_string.parse::()?; - let console_path = format!( - "/local/domain/0/backend/console/{}/{}", - domid_string, id_string - ); - self.ensure_backend_exists(domid, id, console_path).await?; - seen.push(domid); - } - } - - let mut gone: Vec = Vec::new(); - for backend in self.backends.keys() { - if !seen.contains(backend) { - gone.push(*backend); - } - } - - for item in gone { - if let Some(backend) = self.backends.remove(&item) { - drop(backend); - } - } - - Ok(()) - } -} - -pub struct ChannelBackend { - pub domid: u32, - pub id: u32, - pub sender: Sender>, - raw_sender: Sender<(u32, Option>)>, - task: JoinHandle<()>, -} - -impl Drop for ChannelBackend { - fn drop(&mut self) { - self.task.abort(); - let _ = self.raw_sender.try_send((self.domid, None)); - debug!( - "destroyed channel backend for domain {} channel {}", - self.domid, self.id - ); - } -} - -impl ChannelBackend { - #[allow(clippy::too_many_arguments)] - pub async fn new( - backend: String, - frontend: String, - domid: u32, - id: u32, - store: XsdClient, - evtchn: EventChannelService, - gnttab: GrantTab, - output_sender: Sender<(u32, Option>)>, - use_reserved_ref: Option, - ) -> Result { - let processor = KrataChannelBackendProcessor { - backend, - frontend, - domid, - id, - store, - evtchn, - gnttab, - use_reserved_ref, - }; - - let (input_sender, input_receiver) = channel(SINGLE_CHANNEL_QUEUE_LEN); - - let task = processor - .launch(output_sender.clone(), input_receiver) - .await?; - Ok(ChannelBackend { - domid, - id, - task, - raw_sender: output_sender, - sender: input_sender, - }) - } -} - -#[derive(Clone)] -pub struct KrataChannelBackendProcessor { - use_reserved_ref: Option, - backend: String, - frontend: String, - id: u32, - domid: u32, - store: XsdClient, - evtchn: EventChannelService, - gnttab: GrantTab, -} - -impl KrataChannelBackendProcessor { - async fn init(&self) -> Result<()> { - self.store - .write_string(format!("{}/state", self.backend), "3") - .await?; - debug!( - "created channel backend for domain {} channel {}", - self.domid, self.id - ); - Ok(()) - } - - async fn on_frontend_state_change(&self) -> Result { - let state = self - .store - .read_string(format!("{}/state", self.backend)) - .await? - .unwrap_or("0".to_string()) - .parse::()?; - if state == 3 { - return Ok(true); - } - Ok(false) - } - - async fn on_self_state_change(&self) -> Result { - let state = self - .store - .read_string(format!("{}/state", self.backend)) - .await? - .unwrap_or("0".to_string()) - .parse::()?; - if state == 5 { - return Ok(true); - } - Ok(false) - } - - async fn launch( - &self, - output_sender: Sender<(u32, Option>)>, - input_receiver: Receiver>, - ) -> Result> { - let owned = self.clone(); - Ok(tokio::task::spawn(async move { - if let Err(error) = owned.processor(output_sender, input_receiver).await { - error!("failed to process krata channel: {}", error); - } - let _ = owned - .store - .write_string(format!("{}/state", owned.backend), "6") - .await; - })) - } - - async fn processor( - &self, - sender: Sender<(u32, Option>)>, - mut receiver: Receiver>, - ) -> Result<()> { - self.init().await?; - let mut frontend_state_change = self - .store - .create_watch(format!("{}/state", self.frontend)) - .await?; - self.store.bind_watch(&frontend_state_change).await?; - - let (ring_ref, port) = loop { - match frontend_state_change.receiver.recv().await { - Some(_) => { - if self.on_frontend_state_change().await? { - let mut tries = 0; - let (ring_ref, port) = loop { - let ring_ref = self - .store - .read_string(format!("{}/ring-ref", self.frontend)) - .await?; - let port = self - .store - .read_string(format!("{}/port", self.frontend)) - .await?; - - if (ring_ref.is_none() || port.is_none()) && tries < 40 { - tries += 1; - self.store - .write_string(format!("{}/state", self.backend), "4") - .await?; - sleep(Duration::from_millis(250)).await; - continue; - } - break (ring_ref, port); - }; - - if ring_ref.is_none() || port.is_none() { - return Err(anyhow!("frontend did not give ring-ref and port")); - } - - let Ok(mut ring_ref) = ring_ref.unwrap().parse::() else { - return Err(anyhow!("frontend gave invalid ring-ref")); - }; - - let Ok(port) = port.unwrap().parse::() else { - return Err(anyhow!("frontend gave invalid port")); - }; - - ring_ref = self.use_reserved_ref.unwrap_or(ring_ref); - debug!( - "channel backend for domain {} channel {}: ring-ref={} port={}", - self.domid, self.id, ring_ref, port, - ); - break (ring_ref, port); - } - } - - None => { - return Ok(()); - } - } - }; - - self.store - .write_string(format!("{}/state", self.backend), "4") - .await?; - let memory = self - .gnttab - .map_grant_refs( - vec![GrantRef { - domid: self.domid, - reference: ring_ref as u32, - }], - true, - true, - ) - .map_err(|e| { - anyhow!( - "failed to map grant ref {} for domid {}: {}", - ring_ref, - self.domid, - e - ) - })?; - let mut channel = self.evtchn.bind(self.domid, port).await?; - unsafe { - let buffer = self.read_output_buffer(channel.local_port, &memory).await?; - if !buffer.is_empty() { - sender.send((self.domid, Some(buffer))).await?; - } - }; - - let mut self_state_change = self - .store - .create_watch(format!("{}/state", self.backend)) - .await?; - self.store.bind_watch(&self_state_change).await?; - loop { - select! { - x = self_state_change.receiver.recv() => match x { - Some(_) => { - match self.on_self_state_change().await { - Err(error) => { - error!("failed to process state change for domain {} channel {}: {}", self.domid, self.id, error); - }, - - Ok(stop) => { - if stop { - break; - } - } - } - }, - - None => { - break; - } - }, - - x = receiver.recv() => match x { - Some(data) => { - let mut index = 0; - loop { - if index >= data.len() { - break; - } - let interface = memory.ptr() as *mut XenConsoleInterface; - let cons = unsafe { (*interface).in_cons }; - let mut prod = unsafe { (*interface).in_prod }; - fence(Ordering::Release); - let space = (prod - cons) as usize; - if space > XenConsoleInterface::INPUT_SIZE { - error!("channel for domid {} has an invalid input space of {}", self.domid, space); - } - let free = XenConsoleInterface::INPUT_SIZE.wrapping_sub(space); - if free == 0 { - sleep(Duration::from_micros(100)).await; - continue; - } - let want = data.len().min(free); - let buffer = &data[index..want]; - for b in buffer { - unsafe { (*interface).input[prod as usize & (XenConsoleInterface::INPUT_SIZE - 1)] = *b; }; - prod = prod.wrapping_add(1); - } - fence(Ordering::Release); - unsafe { (*interface).in_prod = prod; }; - self.evtchn.notify(channel.local_port).await?; - index += want; - } - }, - - None => { - break; - } - }, - - x = channel.receiver.recv() => match x { - Some(_) => { - unsafe { - let buffer = self.read_output_buffer(channel.local_port, &memory).await?; - if !buffer.is_empty() { - sender.send((self.domid, Some(buffer))).await?; - } - }; - channel.unmask().await?; - }, - - None => { - break; - } - } - } - } - Ok(()) - } - - async unsafe fn read_output_buffer<'a>( - &self, - local_port: u32, - memory: &MappedMemory<'a>, - ) -> Result> { - let interface = memory.ptr() as *mut XenConsoleInterface; - let mut cons = (*interface).out_cons; - let prod = (*interface).out_prod; - fence(Ordering::Release); - let size = prod.wrapping_sub(cons); - let mut data: Vec = Vec::new(); - if size == 0 || size as usize > XenConsoleInterface::OUTPUT_SIZE { - return Ok(data); - } - loop { - if cons == prod { - break; - } - data.push((*interface).output[cons as usize & (XenConsoleInterface::OUTPUT_SIZE - 1)]); - cons = cons.wrapping_add(1); - } - fence(Ordering::AcqRel); - (*interface).out_cons = cons; - self.evtchn.notify(local_port).await?; - Ok(data) - } -} diff --git a/crates/runtime/src/ip.rs b/crates/runtime/src/ip.rs deleted file mode 100644 index 4c7771b6..00000000 --- a/crates/runtime/src/ip.rs +++ /dev/null @@ -1,337 +0,0 @@ -use std::{ - collections::HashMap, - net::{Ipv4Addr, Ipv6Addr}, - str::FromStr, - sync::Arc, -}; - -use anyhow::{anyhow, Result}; -use ipnetwork::{Ipv4Network, Ipv6Network}; -use log::{debug, error}; -use tokio::sync::RwLock; -use uuid::Uuid; -use xenstore::{XsdClient, XsdInterface}; - -#[derive(Default, Clone)] -pub struct IpVendorState { - pub ipv4: HashMap, - pub ipv6: HashMap, - pub pending_ipv4: HashMap, - pub pending_ipv6: HashMap, -} - -#[derive(Clone)] -pub struct IpVendor { - store: XsdClient, - host_uuid: Uuid, - ipv4_network: Ipv4Network, - ipv6_network: Ipv6Network, - gateway_ipv4: Ipv4Addr, - gateway_ipv6: Ipv6Addr, - state: Arc>, -} - -pub struct IpAssignment { - vendor: IpVendor, - pub uuid: Uuid, - pub ipv4: Ipv4Addr, - pub ipv6: Ipv6Addr, - pub ipv4_prefix: u8, - pub ipv6_prefix: u8, - pub gateway_ipv4: Ipv4Addr, - pub gateway_ipv6: Ipv6Addr, - pub committed: bool, -} - -impl IpAssignment { - pub async fn commit(&mut self) -> Result<()> { - self.vendor.commit(self).await?; - self.committed = true; - Ok(()) - } -} - -impl Drop for IpAssignment { - fn drop(&mut self) { - if !self.committed { - let ipv4 = self.ipv4; - let ipv6 = self.ipv6; - let uuid = self.uuid; - let vendor = self.vendor.clone(); - tokio::task::spawn(async move { - let _ = vendor.recall_raw(ipv4, ipv6, uuid, true).await; - }); - } - } -} - -impl IpVendor { - pub async fn new( - store: XsdClient, - host_uuid: Uuid, - ipv4_network: Ipv4Network, - ipv6_network: Ipv6Network, - ) -> Result { - debug!("fetching state from xenstore"); - let mut state = IpVendor::fetch_stored_state(&store).await?; - debug!("allocating IP set"); - let (gateway_ipv4, gateway_ipv6) = - IpVendor::allocate_ipset(&mut state, host_uuid, ipv4_network, ipv6_network)?; - let vend = IpVendor { - store, - host_uuid, - ipv4_network, - ipv6_network, - gateway_ipv4, - gateway_ipv6, - state: Arc::new(RwLock::new(state)), - }; - debug!("IP vendor initialized!"); - Ok(vend) - } - - async fn fetch_stored_state(store: &XsdClient) -> Result { - debug!("initializing default IP vendor state"); - let mut state = IpVendorState::default(); - debug!("iterating over xen domains"); - for domid_candidate in store.list("/local/domain").await? { - let dom_path = format!("/local/domain/{}", domid_candidate); - let Some(uuid) = store - .read_string(format!("{}/krata/uuid", dom_path)) - .await? - .and_then(|x| Uuid::from_str(&x).ok()) - else { - continue; - }; - let assigned_ipv4 = store - .read_string(format!("{}/krata/network/zone/ipv4", dom_path)) - .await? - .and_then(|x| Ipv4Network::from_str(&x).ok()); - let assigned_ipv6 = store - .read_string(format!("{}/krata/network/zone/ipv6", dom_path)) - .await? - .and_then(|x| Ipv6Network::from_str(&x).ok()); - - if let Some(existing_ipv4) = assigned_ipv4 { - if let Some(previous) = state.ipv4.insert(existing_ipv4.ip(), uuid) { - error!("ipv4 conflict detected: zone {} owned {} but {} also claimed to own it, giving it to {}", previous, existing_ipv4.ip(), uuid, uuid); - } - } - - if let Some(existing_ipv6) = assigned_ipv6 { - if let Some(previous) = state.ipv6.insert(existing_ipv6.ip(), uuid) { - error!("ipv6 conflict detected: zone {} owned {} but {} also claimed to own it, giving it to {}", previous, existing_ipv6.ip(), uuid, uuid); - } - } - } - debug!("IP state hydrated"); - Ok(state) - } - - fn allocate_ipset( - state: &mut IpVendorState, - uuid: Uuid, - ipv4_network: Ipv4Network, - ipv6_network: Ipv6Network, - ) -> Result<(Ipv4Addr, Ipv6Addr)> { - let mut found_ipv4: Option = None; - for ip in ipv4_network.iter() { - if ip.is_loopback() || ip.is_multicast() || ip.is_broadcast() { - continue; - } - - if !ip.is_private() { - continue; - } - - let last = ip.octets()[3]; - if last == 0 || last > 250 { - continue; - } - - if state.ipv4.contains_key(&ip) { - continue; - } - found_ipv4 = Some(ip); - break; - } - - let mut found_ipv6: Option = None; - for ip in ipv6_network.iter() { - if ip.is_loopback() || ip.is_multicast() { - continue; - } - - if state.ipv6.contains_key(&ip) { - continue; - } - found_ipv6 = Some(ip); - break; - } - - let Some(ipv4) = found_ipv4 else { - return Err(anyhow!( - "unable to allocate ipv4 address, assigned network is exhausted" - )); - }; - - let Some(ipv6) = found_ipv6 else { - return Err(anyhow!( - "unable to allocate ipv6 address, assigned network is exhausted" - )); - }; - - state.ipv4.insert(ipv4, uuid); - state.ipv6.insert(ipv6, uuid); - - Ok((ipv4, ipv6)) - } - - pub async fn assign(&self, uuid: Uuid) -> Result { - let mut state = self.state.write().await; - let (ipv4, ipv6) = - IpVendor::allocate_ipset(&mut state, uuid, self.ipv4_network, self.ipv6_network)?; - state.pending_ipv4.insert(ipv4, uuid); - state.pending_ipv6.insert(ipv6, uuid); - Ok(IpAssignment { - vendor: self.clone(), - uuid, - ipv4, - ipv6, - ipv4_prefix: self.ipv4_network.prefix(), - ipv6_prefix: self.ipv6_network.prefix(), - gateway_ipv4: self.gateway_ipv4, - gateway_ipv6: self.gateway_ipv6, - committed: false, - }) - } - - pub async fn commit(&self, assignment: &IpAssignment) -> Result<()> { - let mut state = self.state.write().await; - if state.pending_ipv4.remove(&assignment.ipv4) != Some(assignment.uuid) { - return Err(anyhow!("matching pending ipv4 assignment was not found")); - } - if state.pending_ipv6.remove(&assignment.ipv6) != Some(assignment.uuid) { - return Err(anyhow!("matching pending ipv6 assignment was not found")); - } - Ok(()) - } - - async fn recall_raw( - &self, - ipv4: Ipv4Addr, - ipv6: Ipv6Addr, - uuid: Uuid, - pending: bool, - ) -> Result<()> { - let mut state = self.state.write().await; - if pending { - if state.pending_ipv4.remove(&ipv4) != Some(uuid) { - return Err(anyhow!("matching pending ipv4 assignment was not found")); - } - if state.pending_ipv6.remove(&ipv6) != Some(uuid) { - return Err(anyhow!("matching pending ipv6 assignment was not found")); - } - } - - if state.ipv4.remove(&ipv4) != Some(uuid) { - return Err(anyhow!("matching allocated ipv4 assignment was not found")); - } - - if state.ipv6.remove(&ipv6) != Some(uuid) { - return Err(anyhow!("matching allocated ipv6 assignment was not found")); - } - Ok(()) - } - - pub async fn recall(&self, assignment: &IpAssignment) -> Result<()> { - self.recall_raw(assignment.ipv4, assignment.ipv6, assignment.uuid, false) - .await?; - Ok(()) - } - - pub async fn reload(&self) -> Result<()> { - let mut state = self.state.write().await; - let mut intermediate = IpVendor::fetch_stored_state(&self.store).await?; - intermediate.ipv4.insert(self.gateway_ipv4, self.host_uuid); - intermediate.ipv6.insert(self.gateway_ipv6, self.host_uuid); - for (ipv4, uuid) in &state.pending_ipv4 { - if let Some(previous) = intermediate.ipv4.insert(*ipv4, *uuid) { - error!("ipv4 conflict detected: zone {} owned (pending) {} but {} also claimed to own it, giving it to {}", previous, ipv4, uuid, uuid); - } - intermediate.pending_ipv4.insert(*ipv4, *uuid); - } - for (ipv6, uuid) in &state.pending_ipv6 { - if let Some(previous) = intermediate.ipv6.insert(*ipv6, *uuid) { - error!("ipv6 conflict detected: zone {} owned (pending) {} but {} also claimed to own it, giving it to {}", previous, ipv6, uuid, uuid); - } - intermediate.pending_ipv6.insert(*ipv6, *uuid); - } - *state = intermediate; - Ok(()) - } - - pub async fn read_domain_assignment( - &self, - uuid: Uuid, - domid: u32, - ) -> Result> { - let dom_path = format!("/local/domain/{}", domid); - let Some(zone_ipv4) = self - .store - .read_string(format!("{}/krata/network/zone/ipv4", dom_path)) - .await? - else { - return Ok(None); - }; - let Some(zone_ipv6) = self - .store - .read_string(format!("{}/krata/network/zone/ipv6", dom_path)) - .await? - else { - return Ok(None); - }; - let Some(gateway_ipv4) = self - .store - .read_string(format!("{}/krata/network/gateway/ipv4", dom_path)) - .await? - else { - return Ok(None); - }; - let Some(gateway_ipv6) = self - .store - .read_string(format!("{}/krata/network/gateway/ipv6", dom_path)) - .await? - else { - return Ok(None); - }; - - let Some(zone_ipv4) = Ipv4Network::from_str(&zone_ipv4).ok() else { - return Ok(None); - }; - let Some(zone_ipv6) = Ipv6Network::from_str(&zone_ipv6).ok() else { - return Ok(None); - }; - let Some(gateway_ipv4) = Ipv4Network::from_str(&gateway_ipv4).ok() else { - return Ok(None); - }; - let Some(gateway_ipv6) = Ipv6Network::from_str(&gateway_ipv6).ok() else { - return Ok(None); - }; - Ok(Some(IpAssignment { - vendor: self.clone(), - uuid, - ipv4: zone_ipv4.ip(), - ipv4_prefix: zone_ipv4.prefix(), - ipv6: zone_ipv6.ip(), - ipv6_prefix: zone_ipv6.prefix(), - gateway_ipv4: gateway_ipv4.ip(), - gateway_ipv6: gateway_ipv6.ip(), - committed: true, - })) - } - - pub async fn read(&self) -> Result { - Ok(self.state.read().await.clone()) - } -} diff --git a/crates/runtime/src/launch.rs b/crates/runtime/src/launch.rs deleted file mode 100644 index 559dbc1d..00000000 --- a/crates/runtime/src/launch.rs +++ /dev/null @@ -1,252 +0,0 @@ -use std::collections::HashMap; -use std::fs; -use std::path::PathBuf; -use std::sync::Arc; - -use advmac::MacAddr6; -use anyhow::{anyhow, Result}; -use tokio::sync::Semaphore; -use uuid::Uuid; - -use krata::launchcfg::{ - LaunchInfo, LaunchNetwork, LaunchNetworkIpv4, LaunchNetworkIpv6, LaunchNetworkResolver, - LaunchPackedFormat, LaunchRoot, -}; -use krataoci::packer::OciPackedImage; -pub use xenclient::{ - pci::PciBdf, DomainPciDevice as PciDevice, DomainPciRdmReservePolicy as PciRdmReservePolicy, -}; -use xenclient::{DomainChannel, DomainConfig, DomainDisk, DomainNetworkInterface}; -use xenplatform::domain::BaseDomainConfig; - -use crate::cfgblk::ConfigBlock; -use crate::RuntimeContext; - -use super::{ZoneInfo, ZoneState}; - -pub struct ZoneLaunchRequest { - pub format: LaunchPackedFormat, - pub kernel: Vec, - pub initrd: Vec, - pub uuid: Option, - pub name: Option, - pub target_cpus: u32, - pub max_cpus: u32, - pub target_memory: u64, - pub max_memory: u64, - pub env: HashMap, - pub run: Option>, - pub pcis: Vec, - pub kernel_verbose: bool, - pub kernel_cmdline_append: String, - pub image: OciPackedImage, - pub addons_image: Option, - pub network: ZoneLaunchNetwork, -} - -pub struct ZoneLaunchNetwork { - pub ipv4: String, - pub ipv4_prefix: u8, - pub ipv6: String, - pub ipv6_prefix: u8, - pub gateway_ipv4: String, - pub gateway_ipv6: String, - pub zone_mac: MacAddr6, - pub nameservers: Vec, -} - -pub struct ZoneLauncher { - pub launch_semaphore: Arc, -} - -impl ZoneLauncher { - pub fn new(launch_semaphore: Arc) -> Result { - Ok(Self { launch_semaphore }) - } - - pub async fn launch( - &mut self, - context: &RuntimeContext, - request: ZoneLaunchRequest, - ) -> Result { - let uuid = request.uuid.unwrap_or_else(Uuid::new_v4); - let xen_name = format!("krata-{uuid}"); - let _launch_permit = self.launch_semaphore.acquire().await?; - let launch_config = LaunchInfo { - root: LaunchRoot { - format: request.format.clone(), - }, - hostname: Some( - request - .name - .as_ref() - .map(|x| x.to_string()) - .unwrap_or_else(|| format!("krata-{}", uuid)), - ), - network: Some(LaunchNetwork { - link: "eth0".to_string(), - ipv4: LaunchNetworkIpv4 { - address: format!("{}/{}", request.network.ipv4, request.network.ipv4_prefix), - gateway: request.network.gateway_ipv4, - }, - ipv6: LaunchNetworkIpv6 { - address: format!("{}/{}", request.network.ipv6, request.network.ipv6_prefix), - gateway: request.network.gateway_ipv6.to_string(), - }, - resolver: LaunchNetworkResolver { - nameservers: request.network.nameservers, - }, - }), - env: request.env, - run: request.run, - }; - - let cfgblk = ConfigBlock::new(&uuid, request.image.clone())?; - let cfgblk_file = cfgblk.file.clone(); - let cfgblk_dir = cfgblk.dir.clone(); - tokio::task::spawn_blocking(move || cfgblk.build(&launch_config)).await??; - - let image_squashfs_path = request - .image - .path - .to_str() - .ok_or_else(|| anyhow!("failed to convert image path to string"))?; - - let cfgblk_dir_path = cfgblk_dir - .to_str() - .ok_or_else(|| anyhow!("failed to convert cfgblk directory path to string"))?; - let cfgblk_squashfs_path = cfgblk_file - .to_str() - .ok_or_else(|| anyhow!("failed to convert cfgblk squashfs path to string"))?; - let addons_squashfs_path = request - .addons_image - .map(|x| x.to_str().map(|x| x.to_string())) - .map(|x| { - Some(x.ok_or_else(|| anyhow!("failed to convert addons squashfs path to string"))) - }) - .unwrap_or(None); - - let addons_squashfs_path = if let Some(path) = addons_squashfs_path { - Some(path?) - } else { - None - }; - - let image_squashfs_loop = context.autoloop.loopify(image_squashfs_path)?; - let cfgblk_squashfs_loop = context.autoloop.loopify(cfgblk_squashfs_path)?; - let addons_squashfs_loop = if let Some(ref addons_squashfs_path) = addons_squashfs_path { - Some(context.autoloop.loopify(addons_squashfs_path)?) - } else { - None - }; - let mut cmdline_options = ["console=hvc0"].to_vec(); - if !request.kernel_verbose { - cmdline_options.push("quiet"); - } - - if !request.kernel_cmdline_append.is_empty() { - cmdline_options.push(&request.kernel_cmdline_append); - } - - let cmdline = cmdline_options.join(" "); - - let zone_mac_string = request.network.zone_mac.to_string().replace('-', ":"); - - let mut disks = vec![ - DomainDisk { - vdev: "xvda".to_string(), - block: image_squashfs_loop.clone(), - writable: false, - }, - DomainDisk { - vdev: "xvdb".to_string(), - block: cfgblk_squashfs_loop.clone(), - writable: false, - }, - ]; - - if let Some(ref addons) = addons_squashfs_loop { - disks.push(DomainDisk { - vdev: "xvdc".to_string(), - block: addons.clone(), - writable: false, - }); - } - - let mut loops = vec![ - format!("{}:{}:none", image_squashfs_loop.path, image_squashfs_path), - format!( - "{}:{}:{}", - cfgblk_squashfs_loop.path, cfgblk_squashfs_path, cfgblk_dir_path - ), - ]; - - if let Some(ref addons) = addons_squashfs_loop { - loops.push(format!( - "{}:{}:none", - addons.path, - addons_squashfs_path - .clone() - .ok_or_else(|| anyhow!("addons squashfs path missing"))? - )); - } - - let mut extra_keys = vec![ - ("krata/uuid".to_string(), uuid.to_string()), - ("krata/loops".to_string(), loops.join(",")), - ]; - - if let Some(name) = request.name.as_ref() { - extra_keys.push(("krata/name".to_string(), name.clone())); - } - - let config = DomainConfig { - base: BaseDomainConfig { - max_vcpus: request.max_cpus, - target_vcpus: request.target_cpus, - max_mem_mb: request.max_memory, - target_mem_mb: request.target_memory, - kernel: request.kernel, - initrd: request.initrd, - cmdline, - uuid, - owner_domid: 0, - enable_iommu: !request.pcis.is_empty(), - }, - backend_domid: 0, - name: xen_name, - swap_console_backend: Some("krata-console".to_string()), - disks, - channels: vec![DomainChannel { - typ: "krata-channel".to_string(), - initialized: false, - }], - vifs: vec![DomainNetworkInterface { - mac: zone_mac_string.clone(), - mtu: 1500, - bridge: None, - script: None, - }], - pcis: request.pcis.clone(), - filesystems: vec![], - extra_keys, - extra_rw_paths: vec!["krata/zone".to_string()], - }; - match context.xen.create(&config).await { - Ok(created) => Ok(ZoneInfo { - name: request.name.as_ref().map(|x| x.to_string()), - uuid, - domid: created.domid, - image: request.image.digest, - loops: vec![], - state: ZoneState { exit_code: None }, - }), - Err(error) => { - let _ = context.autoloop.unloop(&image_squashfs_loop.path).await; - let _ = context.autoloop.unloop(&cfgblk_squashfs_loop.path).await; - let _ = fs::remove_dir(&cfgblk_dir); - Err(error.into()) - } - } - } -} diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs deleted file mode 100644 index 8bba84ce..00000000 --- a/crates/runtime/src/lib.rs +++ /dev/null @@ -1,320 +0,0 @@ -use anyhow::{anyhow, Result}; -use krataloopdev::LoopControl; -use log::debug; -use std::{fs, path::PathBuf, str::FromStr, sync::Arc}; -use tokio::sync::Semaphore; -use uuid::Uuid; - -use xenclient::XenClient; -use xenplatform::domain::XEN_EXTRA_MEMORY_KB; -use xenstore::{XsdClient, XsdInterface}; - -use self::{ - autoloop::AutoLoop, - launch::{ZoneLaunchRequest, ZoneLauncher}, - power::PowerManagementContext, -}; - -pub mod autoloop; -pub mod cfgblk; -pub mod channel; -pub mod launch; -pub mod power; - -#[cfg(target_arch = "x86_64")] -type RuntimePlatform = xenplatform::x86pv::X86PvPlatform; - -#[cfg(not(target_arch = "x86_64"))] -type RuntimePlatform = xenplatform::unsupported::UnsupportedPlatform; - -#[derive(Clone)] -pub struct ZoneLoopInfo { - pub device: String, - pub file: String, - pub delete: Option, -} - -#[derive(Clone)] -pub struct ZoneState { - pub exit_code: Option, -} - -#[derive(Clone)] -pub struct ZoneInfo { - pub name: Option, - pub uuid: Uuid, - pub domid: u32, - pub image: String, - pub loops: Vec, - pub state: ZoneState, -} - -#[derive(Clone)] -pub struct RuntimeContext { - pub autoloop: AutoLoop, - pub xen: XenClient, -} - -impl RuntimeContext { - pub async fn new() -> Result { - let xen = XenClient::new(0, RuntimePlatform::new()).await?; - Ok(RuntimeContext { - autoloop: AutoLoop::new(LoopControl::open()?), - xen, - }) - } - - pub async fn list(&self) -> Result> { - let mut zones: Vec = Vec::new(); - for domid_candidate in self.xen.store.list("/local/domain").await? { - if domid_candidate == "0" { - continue; - } - let dom_path = format!("/local/domain/{}", domid_candidate); - let uuid_string = match self - .xen - .store - .read_string(&format!("{}/krata/uuid", &dom_path)) - .await? - { - None => continue, - Some(value) => value, - }; - let domid = - u32::from_str(&domid_candidate).map_err(|_| anyhow!("failed to parse domid"))?; - let uuid = Uuid::from_str(&uuid_string)?; - - let name = self - .xen - .store - .read_string(&format!("{}/krata/name", &dom_path)) - .await?; - - let image = self - .xen - .store - .read_string(&format!("{}/krata/image", &dom_path)) - .await? - .unwrap_or("unknown".to_string()); - let loops = self - .xen - .store - .read_string(&format!("{}/krata/loops", &dom_path)) - .await?; - let exit_code = self - .xen - .store - .read_string(&format!("{}/krata/zone/exit-code", &dom_path)) - .await?; - - let exit_code: Option = match exit_code { - Some(code) => code.parse().ok(), - None => None, - }; - - let state = ZoneState { exit_code }; - - let loops = RuntimeContext::parse_loop_set(&loops); - zones.push(ZoneInfo { - name, - uuid, - domid, - image, - loops, - state, - }); - } - Ok(zones) - } - - pub async fn resolve(&self, uuid: Uuid) -> Result> { - for zone in self.list().await? { - if zone.uuid == uuid { - return Ok(Some(zone)); - } - } - Ok(None) - } - - fn parse_loop_set(input: &Option) -> Vec { - let Some(input) = input else { - return Vec::new(); - }; - let sets = input - .split(',') - .map(|x| x.to_string()) - .map(|x| x.split(':').map(|v| v.to_string()).collect::>()) - .map(|x| (x[0].clone(), x[1].clone(), x[2].clone())) - .collect::>(); - sets.iter() - .map(|(device, file, delete)| ZoneLoopInfo { - device: device.clone(), - file: file.clone(), - delete: if delete == "none" { - None - } else { - Some(delete.clone()) - }, - }) - .collect::>() - } -} - -#[derive(Clone)] -pub struct Runtime { - context: RuntimeContext, - launch_semaphore: Arc, -} - -impl Runtime { - pub async fn new() -> Result { - let context = RuntimeContext::new().await?; - debug!("testing for hypervisor presence"); - context - .xen - .call - .get_version_capabilities() - .await - .map_err(|_| anyhow!("hypervisor is not present"))?; - Ok(Self { - context, - launch_semaphore: Arc::new(Semaphore::new(10)), - }) - } - - pub async fn launch(&self, request: ZoneLaunchRequest) -> Result { - let mut launcher = ZoneLauncher::new(self.launch_semaphore.clone())?; - launcher.launch(&self.context, request).await - } - - pub async fn destroy(&self, uuid: Uuid) -> Result { - let info = self - .context - .resolve(uuid) - .await? - .ok_or_else(|| anyhow!("unable to resolve zone: {}", uuid))?; - let domid = info.domid; - let store = XsdClient::open().await?; - let dom_path = store.get_domain_path(domid).await?; - let uuid = match store - .read_string(format!("{}/krata/uuid", dom_path).as_str()) - .await? - { - None => { - return Err(anyhow!( - "domain {} was not found or not created by krata", - domid - )) - } - Some(value) => value, - }; - if uuid.is_empty() { - return Err(anyhow!("unable to find krata uuid based on the domain",)); - } - let uuid = Uuid::parse_str(&uuid)?; - let loops = store - .read_string(format!("{}/krata/loops", dom_path).as_str()) - .await?; - let loops = RuntimeContext::parse_loop_set(&loops); - self.context.xen.destroy(domid).await?; - for info in &loops { - self.context.autoloop.unloop(&info.device).await?; - match &info.delete { - None => {} - Some(delete) => { - let delete_path = PathBuf::from(delete); - if delete_path.is_file() || delete_path.is_symlink() { - fs::remove_file(&delete_path)?; - } else if delete_path.is_dir() { - fs::remove_dir_all(&delete_path)?; - } - } - } - } - Ok(uuid) - } - - pub async fn set_memory_resources( - &self, - domid: u32, - target_memory_bytes: u64, - max_memory_bytes: u64, - ) -> Result<()> { - let mut max_memory_bytes = max_memory_bytes + (XEN_EXTRA_MEMORY_KB * 1024); - if target_memory_bytes > max_memory_bytes { - max_memory_bytes = target_memory_bytes + (XEN_EXTRA_MEMORY_KB * 1024); - } - - self.context - .xen - .call - .set_max_mem(domid, max_memory_bytes / 1024) - .await?; - let domain_path = self.context.xen.store.get_domain_path(domid).await?; - let tx = self.context.xen.store.transaction().await?; - let max_memory_path = format!("{}/memory/static-max", domain_path); - tx.write_string(max_memory_path, &(max_memory_bytes / 1024).to_string()) - .await?; - let target_memory_path = format!("{}/memory/target", domain_path); - tx.write_string( - target_memory_path, - &(target_memory_bytes / 1024).to_string(), - ) - .await?; - tx.commit().await?; - Ok(()) - } - - pub async fn set_cpu_resources(&self, domid: u32, target_cpus: u32) -> Result<()> { - let domain_path = self.context.xen.store.get_domain_path(domid).await?; - let cpus = self - .context - .xen - .store - .list(&format!("{}/cpu", domain_path)) - .await?; - let tx = self.context.xen.store.transaction().await?; - for cpu in cpus { - let Some(id) = cpu.parse::().ok() else { - continue; - }; - let available = if id >= target_cpus { - "offline" - } else { - "online" - }; - tx.write_string( - format!("{}/cpu/{}/availability", domain_path, id), - available, - ) - .await?; - } - tx.commit().await?; - Ok(()) - } - - pub async fn list(&self) -> Result> { - self.context.list().await - } - - pub async fn dupe(&self) -> Result { - Runtime::new().await - } - - pub async fn power_management_context(&self) -> Result { - let context = RuntimeContext::new().await?; - Ok(PowerManagementContext { context }) - } - - pub async fn read_hypervisor_console(&self, clear: bool) -> Result> { - let index = 0_u32; - let (rawbuf, newindex) = self - .context - .xen - .call - .read_console_ring_raw(clear, index) - .await?; - let buf = std::str::from_utf8(&rawbuf[..newindex as usize])?; - Ok(Arc::from(buf)) - } -} diff --git a/crates/runtime/src/power.rs b/crates/runtime/src/power.rs deleted file mode 100644 index aacf4bfd..00000000 --- a/crates/runtime/src/power.rs +++ /dev/null @@ -1,177 +0,0 @@ -use anyhow::Result; -use indexmap::IndexMap; -use log::info; -use xencall::sys::{CpuId, SysctlCputopo}; - -use crate::RuntimeContext; - -#[derive(Clone)] -pub struct PowerManagementContext { - pub context: RuntimeContext, -} - -#[derive(Clone, Copy, Debug)] -pub enum CpuClass { - Standard, - Performance, - Efficiency, -} - -#[derive(Clone, Copy, Debug)] -pub struct CpuTopologyInfo { - pub core: u32, - pub socket: u32, - pub node: u32, - pub thread: u32, - pub class: CpuClass, -} - -fn labeled_topology(input: &[SysctlCputopo]) -> Vec { - let mut cores: IndexMap<(u32, u32, u32), Vec> = IndexMap::new(); - let mut pe_cores = false; - let mut last: Option = None; - - for item in input { - if cores.is_empty() { - cores.insert( - (item.core, item.socket, item.node), - vec![CpuTopologyInfo { - core: item.core, - socket: item.socket, - thread: 0, - node: item.node, - class: CpuClass::Standard, - }], - ); - last = Some(*item); - continue; - } - - if last - .map(|last| { - item.core - .checked_sub(last.core) - .map(|diff| diff >= 3) - .unwrap_or(false) - }) - .unwrap_or(false) - { - // detect if performance cores seem to be kicking in. - if let Some(last) = last { - if let Some(list) = cores.get_mut(&(last.core, last.socket, last.node)) { - for other in list { - other.class = CpuClass::Performance; - } - } - } - let list = cores - .entry((item.core, item.socket, item.node)) - .or_default(); - for old in &mut *list { - old.class = CpuClass::Performance; - } - list.push(CpuTopologyInfo { - core: item.core, - socket: item.socket, - thread: 0, - node: item.node, - class: CpuClass::Performance, - }); - pe_cores = true; - } else if pe_cores && last.map(|last| item.core == last.core + 1).unwrap_or(false) { - // detect efficiency cores if P/E cores are in use. - if let Some(last) = last { - if let Some(list) = cores.get_mut(&(last.core, last.socket, last.node)) { - for other in list { - other.class = CpuClass::Efficiency; - } - } - } - let list = cores - .entry((item.core, item.socket, item.node)) - .or_default(); - list.push(CpuTopologyInfo { - core: item.core, - socket: item.socket, - thread: 0, - node: item.node, - class: CpuClass::Efficiency, - }); - } else { - let list = cores - .entry((item.core, item.socket, item.node)) - .or_default(); - if list.is_empty() { - list.push(CpuTopologyInfo { - core: item.core, - socket: item.socket, - thread: 0, - node: item.node, - class: CpuClass::Standard, - }); - } else { - list.push(CpuTopologyInfo { - core: item.core, - socket: item.socket, - thread: 0, - node: item.node, - class: list - .first() - .map(|first| first.class) - .unwrap_or(CpuClass::Standard), - }); - } - } - last = Some(*item); - } - - for threads in cores.values_mut() { - for (index, thread) in threads.iter_mut().enumerate() { - thread.thread = index as u32; - } - } - - cores.into_values().flatten().collect::>() -} - -impl PowerManagementContext { - /// Get the CPU topology, with SMT awareness. - /// Also translates Intel p-core/e-core nonsense: non-sequential core identifiers - /// are treated as p-cores, while e-cores behave as standard cores. - /// If there is a p-core/e-core split, then CPU class will be defined as - /// `CpuClass::Performance` or `CpuClass::Efficiency`, else `CpuClass::Standard`. - pub async fn cpu_topology(&self) -> Result> { - let xen_topology = self.context.xen.call.cpu_topology().await?; - let logical_topology = labeled_topology(&xen_topology); - Ok(logical_topology) - } - - /// Enable or disable SMT awareness in the scheduler. - pub async fn set_smt_policy(&self, enable: bool) -> Result<()> { - self.context - .xen - .call - .set_turbo_mode(CpuId::All, enable) - .await - .unwrap_or_else(|error| { - info!("non-fatal error while setting SMT policy: {:?}", error); - }); - Ok(()) - } - - /// Set scheduler policy name. - pub async fn set_scheduler_policy(&self, policy: impl AsRef) -> Result<()> { - self.context - .xen - .call - .set_cpufreq_gov(CpuId::All, policy) - .await - .unwrap_or_else(|error| { - info!( - "non-fatal error while setting scheduler policy: {:?}", - error - ); - }); - Ok(()) - } -} diff --git a/crates/zone/Cargo.toml b/crates/zone/Cargo.toml deleted file mode 100644 index 2cba4517..00000000 --- a/crates/zone/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -name = "krata-zone" -description = "zone services for the krata isolation engine" -license.workspace = true -version.workspace = true -homepage.workspace = true -repository.workspace = true -edition = "2021" -resolver = "2" - -[dependencies] -anyhow = { workspace = true } -cgroups-rs = { workspace = true } -env_logger = { workspace = true } -futures = { workspace = true } -ipnetwork = { workspace = true } -krata = { path = "../krata", version = "^0.0.21" } -krata-xenstore = { path = "../xen/xenstore", version = "^0.0.21" } -libc = { workspace = true } -log = { workspace = true } -nix = { workspace = true, features = ["ioctl", "process", "fs"] } -oci-spec = { workspace = true } -path-absolutize = { workspace = true } -platform-info = { workspace = true } -pty-process = { workspace = true, features = ["async"] } -rtnetlink = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -sys-mount = { workspace = true } -sysinfo = { workspace = true } -tokio = { workspace = true } -tokio-util = { workspace = true } - -[lib] -name = "kratazone" - -[[bin]] -name = "krata-zone" -path = "bin/init.rs" diff --git a/crates/zone/bin/init.rs b/crates/zone/bin/init.rs deleted file mode 100644 index f6aecb27..00000000 --- a/crates/zone/bin/init.rs +++ /dev/null @@ -1,19 +0,0 @@ -use anyhow::Result; -use env_logger::Env; -use kratazone::{death, init::ZoneInit}; -use log::error; -use std::env; - -#[tokio::main] -async fn main() -> Result<()> { - env::set_var("RUST_BACKTRACE", "1"); - env_logger::Builder::from_env(Env::default().default_filter_or("warn")).init(); - let mut zone = ZoneInit::new(); - if let Err(error) = zone.init().await { - error!("failed to initialize zone: {}", error); - death(127).await?; - return Ok(()); - } - death(1).await?; - Ok(()) -} diff --git a/crates/zone/src/background.rs b/crates/zone/src/background.rs deleted file mode 100644 index eb73aaa3..00000000 --- a/crates/zone/src/background.rs +++ /dev/null @@ -1,169 +0,0 @@ -use crate::{ - childwait::{ChildEvent, ChildWait}, - death, - exec::ZoneExecTask, - metrics::MetricsCollector, -}; -use anyhow::Result; -use cgroups_rs::Cgroup; -use krata::idm::{ - client::{IdmClientStreamResponseHandle, IdmInternalClient}, - internal::{ - event::Event as EventType, request::Request as RequestType, - response::Response as ResponseType, Event, ExecStreamResponseUpdate, ExitEvent, - MetricsResponse, PingResponse, Request, Response, - }, -}; -use log::debug; -use nix::unistd::Pid; -use tokio::sync::broadcast::Receiver; -use tokio::{select, sync::broadcast}; - -pub struct ZoneBackground { - idm: IdmInternalClient, - child: Pid, - _cgroup: Cgroup, - wait: ChildWait, - child_receiver: Receiver, -} - -impl ZoneBackground { - pub async fn new(idm: IdmInternalClient, cgroup: Cgroup, child: Pid) -> Result { - let (wait, child_receiver) = ChildWait::new()?; - Ok(ZoneBackground { - idm, - child, - _cgroup: cgroup, - wait, - child_receiver, - }) - } - - pub async fn run(&mut self) -> Result<()> { - let mut event_subscription = self.idm.subscribe().await?; - let mut requests_subscription = self.idm.requests().await?; - let mut request_streams_subscription = self.idm.request_streams().await?; - loop { - select! { - x = event_subscription.recv() => match x { - Ok(_event) => { - }, - - Err(broadcast::error::RecvError::Closed) => { - debug!("idm packet channel closed"); - break; - }, - - _ => { - continue; - } - }, - - x = requests_subscription.recv() => match x { - Ok((id, request)) => { - self.handle_idm_request(id, request).await?; - }, - - Err(broadcast::error::RecvError::Closed) => { - debug!("idm packet channel closed"); - break; - }, - - _ => { - continue; - } - }, - - x = request_streams_subscription.recv() => match x { - Ok(handle) => { - self.handle_idm_stream_request(handle).await?; - }, - - Err(broadcast::error::RecvError::Closed) => { - debug!("idm packet channel closed"); - break; - }, - - _ => { - continue; - } - }, - - event = self.child_receiver.recv() => match event { - Ok(event) => self.child_event(event).await?, - Err(_) => { - break; - } - } - } - } - Ok(()) - } - - async fn handle_idm_request(&mut self, id: u64, packet: Request) -> Result<()> { - match packet.request { - Some(RequestType::Ping(_)) => { - self.idm - .respond( - id, - Response { - response: Some(ResponseType::Ping(PingResponse {})), - }, - ) - .await?; - } - - Some(RequestType::Metrics(_)) => { - let metrics = MetricsCollector::new()?; - let root = metrics.collect()?; - let response = Response { - response: Some(ResponseType::Metrics(MetricsResponse { root: Some(root) })), - }; - - self.idm.respond(id, response).await?; - } - - _ => {} - } - Ok(()) - } - - async fn handle_idm_stream_request( - &mut self, - handle: IdmClientStreamResponseHandle, - ) -> Result<()> { - let wait = self.wait.clone(); - if let Some(RequestType::ExecStream(_)) = &handle.initial.request { - tokio::task::spawn(async move { - let exec = ZoneExecTask { wait, handle }; - if let Err(error) = exec.run().await { - let _ = exec - .handle - .respond(Response { - response: Some(ResponseType::ExecStream(ExecStreamResponseUpdate { - exited: true, - error: error.to_string(), - exit_code: -1, - stdout: vec![], - stderr: vec![], - })), - }) - .await; - } - }); - } - Ok(()) - } - - async fn child_event(&mut self, event: ChildEvent) -> Result<()> { - if event.pid == self.child { - self.idm - .emit(Event { - event: Some(EventType::Exit(ExitEvent { code: event.status })), - }) - .await?; - death(event.status).await?; - } - Ok(()) - } -} diff --git a/crates/zone/src/childwait.rs b/crates/zone/src/childwait.rs deleted file mode 100644 index 6efe82f9..00000000 --- a/crates/zone/src/childwait.rs +++ /dev/null @@ -1,98 +0,0 @@ -use anyhow::Result; -use libc::{c_int, waitpid, WEXITSTATUS, WIFEXITED}; -use log::warn; -use nix::unistd::Pid; -use std::thread::sleep; -use std::time::Duration; -use std::{ - ptr::addr_of_mut, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - thread::{self, JoinHandle}, -}; -use tokio::sync::broadcast::{channel, Receiver, Sender}; - -const CHILD_WAIT_QUEUE_LEN: usize = 10; - -#[derive(Clone, Copy, Debug)] -pub struct ChildEvent { - pub pid: Pid, - pub status: c_int, -} - -#[derive(Clone)] -pub struct ChildWait { - sender: Sender, - signal: Arc, - _task: Arc>, -} - -impl ChildWait { - pub fn new() -> Result<(ChildWait, Receiver)> { - let (sender, receiver) = channel(CHILD_WAIT_QUEUE_LEN); - let signal = Arc::new(AtomicBool::new(false)); - let mut processor = ChildWaitTask { - sender: sender.clone(), - signal: signal.clone(), - }; - let task = thread::spawn(move || { - if let Err(error) = processor.process() { - warn!("failed to process child updates: {}", error); - } - }); - Ok(( - ChildWait { - sender, - signal, - _task: Arc::new(task), - }, - receiver, - )) - } - - pub async fn subscribe(&self) -> Result> { - Ok(self.sender.subscribe()) - } -} - -struct ChildWaitTask { - sender: Sender, - signal: Arc, -} - -impl ChildWaitTask { - fn process(&mut self) -> Result<()> { - loop { - let mut status: c_int = 0; - let pid = unsafe { waitpid(-1, addr_of_mut!(status), 0) }; - // pid being -1 indicates an error occurred, wait 100 microseconds to avoid - // overloading the channel. Right now we don't consider any other errors - // but that is fine for now, as waitpid shouldn't ever stop anyway. - if pid == -1 { - sleep(Duration::from_micros(100)); - continue; - } - if WIFEXITED(status) { - let event = ChildEvent { - pid: Pid::from_raw(pid), - status: WEXITSTATUS(status), - }; - let _ = self.sender.send(event); - - if self.signal.load(Ordering::Acquire) { - return Ok(()); - } - } - } - } -} - -impl Drop for ChildWait { - fn drop(&mut self) { - if Arc::strong_count(&self.signal) <= 1 { - self.signal.store(true, Ordering::Release); - } - } -} diff --git a/crates/zone/src/exec.rs b/crates/zone/src/exec.rs deleted file mode 100644 index 065ac9ce..00000000 --- a/crates/zone/src/exec.rs +++ /dev/null @@ -1,336 +0,0 @@ -use std::{collections::HashMap, process::Stdio}; - -use crate::childwait::ChildWait; -use anyhow::{anyhow, Result}; -use krata::idm::{ - client::IdmClientStreamResponseHandle, - internal::{ - exec_stream_request_update::Update, request::Request as RequestType, - ExecStreamResponseUpdate, - }, - internal::{response::Response as ResponseType, Request, Response}, -}; -use libc::c_int; -use pty_process::{Pty, Size}; -use tokio::process::Child; -use tokio::{ - io::{AsyncReadExt, AsyncWriteExt}, - join, - process::Command, - select, -}; -use tokio_util::sync::CancellationToken; - -pub struct ZoneExecTask { - pub wait: ChildWait, - pub handle: IdmClientStreamResponseHandle, -} - -impl ZoneExecTask { - pub async fn run(&self) -> Result<()> { - let mut receiver = self.handle.take().await?; - - let Some(ref request) = self.handle.initial.request else { - return Err(anyhow!("request was empty")); - }; - - let RequestType::ExecStream(update) = request else { - return Err(anyhow!("request was not an exec update")); - }; - - let Some(Update::Start(ref start)) = update.update else { - return Err(anyhow!("first request did not contain a start update")); - }; - - let mut cmd = start.command.clone(); - if cmd.is_empty() { - return Err(anyhow!("command line was empty")); - } - let exe = cmd.remove(0); - let mut env = HashMap::new(); - for entry in &start.environment { - env.insert(entry.key.clone(), entry.value.clone()); - } - - if !env.contains_key("PATH") { - env.insert( - "PATH".to_string(), - "/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin".to_string(), - ); - } - - let dir = if start.working_directory.is_empty() { - "/".to_string() - } else { - start.working_directory.clone() - }; - - let mut wait_subscription = self.wait.subscribe().await?; - - let code: c_int; - if start.tty { - let pty = Pty::new().map_err(|error| anyhow!("unable to allocate pty: {}", error))?; - let size = start - .terminal_size - .map(|x| Size::new(x.rows as u16, x.columns as u16)) - .unwrap_or_else(|| Size::new(24, 80)); - pty.resize(size)?; - let pts = pty - .pts() - .map_err(|error| anyhow!("unable to allocate pts: {}", error))?; - let child = std::panic::catch_unwind(move || { - let pts = pts; - pty_process::Command::new(exe) - .args(cmd) - .envs(env) - .current_dir(dir) - .spawn(&pts) - }) - .map_err(|_| anyhow!("internal error")) - .map_err(|error| anyhow!("failed to spawn: {}", error))??; - let mut child = ChildDropGuard { - inner: child, - kill: true, - }; - let pid = child - .inner - .id() - .ok_or_else(|| anyhow!("pid is not provided"))?; - let (mut read, mut write) = pty.into_split(); - let pty_read_handle = self.handle.clone(); - let pty_read_task = tokio::task::spawn(async move { - let mut stdout_buffer = vec![0u8; 8 * 1024]; - loop { - let Ok(size) = read.read(&mut stdout_buffer).await else { - break; - }; - if size > 0 { - let response = Response { - response: Some(ResponseType::ExecStream(ExecStreamResponseUpdate { - exited: false, - exit_code: 0, - error: String::new(), - stdout: stdout_buffer[0..size].to_vec(), - stderr: vec![], - })), - }; - let _ = pty_read_handle.respond(response).await; - } else { - break; - } - } - }); - - let cancel = CancellationToken::new(); - let stdin_cancel = cancel.clone(); - let stdin_task = tokio::task::spawn(async move { - loop { - let Some(request) = receiver.recv().await else { - stdin_cancel.cancel(); - break; - }; - - let Some(RequestType::ExecStream(update)) = request.request else { - continue; - }; - - match update.update { - Some(Update::Stdin(update)) => { - if !update.data.is_empty() - && write.write_all(&update.data).await.is_err() - { - break; - } - - if update.closed { - break; - } - } - Some(Update::TerminalResize(size)) => { - let _ = write.resize(Size::new(size.rows as u16, size.columns as u16)); - } - _ => { - continue; - } - } - } - }); - - code = loop { - select! { - result = wait_subscription.recv() => match result { - Ok(event) => { - if event.pid.as_raw() as u32 == pid { - child.kill = false; - break event.status; - } - } - _ => { - child.inner.start_kill()?; - child.kill = false; - break -1; - } - }, - _ = cancel.cancelled() => { - child.inner.start_kill()?; - child.kill = false; - break -1; - } - } - }; - - let _ = join!(pty_read_task); - stdin_task.abort(); - } else { - let mut child = std::panic::catch_unwind(|| { - Command::new(exe) - .args(cmd) - .envs(env) - .current_dir(dir) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .kill_on_drop(true) - .spawn() - }) - .map_err(|_| anyhow!("internal error")) - .map_err(|error| anyhow!("failed to spawn: {}", error))??; - - let pid = child.id().ok_or_else(|| anyhow!("pid is not provided"))?; - let mut stdin = child - .stdin - .take() - .ok_or_else(|| anyhow!("stdin was missing"))?; - let mut stdout = child - .stdout - .take() - .ok_or_else(|| anyhow!("stdout was missing"))?; - let mut stderr = child - .stderr - .take() - .ok_or_else(|| anyhow!("stderr was missing"))?; - - let stdout_handle = self.handle.clone(); - let stdout_task = tokio::task::spawn(async move { - let mut stdout_buffer = vec![0u8; 8 * 1024]; - loop { - let Ok(size) = stdout.read(&mut stdout_buffer).await else { - break; - }; - if size > 0 { - let response = Response { - response: Some(ResponseType::ExecStream(ExecStreamResponseUpdate { - exited: false, - exit_code: 0, - error: String::new(), - stdout: stdout_buffer[0..size].to_vec(), - stderr: vec![], - })), - }; - let _ = stdout_handle.respond(response).await; - } else { - break; - } - } - }); - - let stderr_handle = self.handle.clone(); - let stderr_task = tokio::task::spawn(async move { - let mut stderr_buffer = vec![0u8; 8 * 1024]; - loop { - let Ok(size) = stderr.read(&mut stderr_buffer).await else { - break; - }; - if size > 0 { - let response = Response { - response: Some(ResponseType::ExecStream(ExecStreamResponseUpdate { - exited: false, - exit_code: 0, - error: String::new(), - stdout: vec![], - stderr: stderr_buffer[0..size].to_vec(), - })), - }; - let _ = stderr_handle.respond(response).await; - } else { - break; - } - } - }); - - let cancel = CancellationToken::new(); - let stdin_cancel = cancel.clone(); - let stdin_task = tokio::task::spawn(async move { - loop { - let Some(request) = receiver.recv().await else { - stdin_cancel.cancel(); - break; - }; - - let Some(RequestType::ExecStream(update)) = request.request else { - continue; - }; - - let Some(Update::Stdin(update)) = update.update else { - continue; - }; - - if stdin.write_all(&update.data).await.is_err() { - break; - } - } - }); - - let data_task = tokio::task::spawn(async move { - let _ = join!(stdout_task, stderr_task); - stdin_task.abort(); - }); - - code = loop { - select! { - result = wait_subscription.recv() => match result { - Ok(event) => { - if event.pid.as_raw() as u32 == pid { - break event.status; - } - } - _ => { - child.start_kill()?; - break -1; - } - }, - _ = cancel.cancelled() => { - child.start_kill()?; - break -1; - } - } - }; - data_task.await?; - } - let response = Response { - response: Some(ResponseType::ExecStream(ExecStreamResponseUpdate { - exited: true, - exit_code: code, - error: String::new(), - stdout: vec![], - stderr: vec![], - })), - }; - self.handle.respond(response).await?; - - Ok(()) - } -} - -struct ChildDropGuard { - pub inner: Child, - pub kill: bool, -} - -impl Drop for ChildDropGuard { - fn drop(&mut self) { - if self.kill { - drop(self.inner.start_kill()); - } - } -} diff --git a/crates/zone/src/init.rs b/crates/zone/src/init.rs deleted file mode 100644 index 897ebb5d..00000000 --- a/crates/zone/src/init.rs +++ /dev/null @@ -1,668 +0,0 @@ -use anyhow::{anyhow, Result}; -use cgroups_rs::{Cgroup, CgroupPid}; -use futures::stream::TryStreamExt; -use ipnetwork::IpNetwork; -use krata::ethtool::EthtoolHandle; -use krata::idm::client::IdmInternalClient; -use krata::idm::internal::INTERNAL_IDM_CHANNEL; -use krata::launchcfg::{LaunchInfo, LaunchNetwork, LaunchPackedFormat}; -use libc::{sethostname, setsid, TIOCSCTTY}; -use log::{trace, warn}; -use nix::ioctl_write_int_bad; -use nix::unistd::{dup2, execve, fork, ForkResult, Pid}; -use oci_spec::image::{Config, ImageConfiguration}; -use path_absolutize::Absolutize; -use platform_info::{PlatformInfo, PlatformInfoAPI, UNameAPI}; -use std::collections::HashMap; -use std::ffi::CString; -use std::fs::{File, OpenOptions, Permissions}; -use std::io; -use std::net::{Ipv4Addr, Ipv6Addr}; -use std::os::fd::AsRawFd; -use std::os::unix::ffi::OsStrExt; -use std::os::unix::fs::{chroot, PermissionsExt}; -use std::path::{Path, PathBuf}; -use std::str::FromStr; -use sys_mount::{FilesystemType, Mount, MountFlags}; -use tokio::fs; - -use crate::background::ZoneBackground; - -const IMAGE_BLOCK_DEVICE_PATH: &str = "/dev/xvda"; -const CONFIG_BLOCK_DEVICE_PATH: &str = "/dev/xvdb"; - -const IMAGE_MOUNT_PATH: &str = "/image"; -const CONFIG_MOUNT_PATH: &str = "/config"; -const OVERLAY_MOUNT_PATH: &str = "/overlay"; - -const OVERLAY_IMAGE_BIND_PATH: &str = "/overlay/image"; -const OVERLAY_WORK_PATH: &str = "/overlay/work"; -const OVERLAY_UPPER_PATH: &str = "/overlay/upper"; - -const SYS_PATH: &str = "/sys"; -const PROC_PATH: &str = "/proc"; -const DEV_PATH: &str = "/dev"; - -const NEW_ROOT_PATH: &str = "/newroot"; -const NEW_ROOT_SYS_PATH: &str = "/newroot/sys"; -const NEW_ROOT_PROC_PATH: &str = "/newroot/proc"; -const NEW_ROOT_DEV_PATH: &str = "/newroot/dev"; - -const IMAGE_CONFIG_JSON_PATH: &str = "/config/image/config.json"; -const LAUNCH_CONFIG_JSON_PATH: &str = "/config/launch.json"; - -const ADDONS_DEVICE_PATH: &str = "/dev/xvdc"; -const ADDONS_MOUNT_PATH: &str = "/addons"; -const ADDONS_MODULES_PATH: &str = "/addons/modules"; - -ioctl_write_int_bad!(set_controlling_terminal, TIOCSCTTY); - -pub struct ZoneInit {} - -impl Default for ZoneInit { - fn default() -> Self { - Self::new() - } -} - -impl ZoneInit { - pub fn new() -> ZoneInit { - ZoneInit {} - } - - pub async fn init(&mut self) -> Result<()> { - self.early_init().await?; - - trace!("opening console descriptor"); - match OpenOptions::new() - .read(true) - .write(true) - .open("/dev/console") - { - Ok(console) => self.map_console(&console)?, - Err(error) => warn!("failed to open console: {}", error), - }; - - let idm = IdmInternalClient::open(INTERNAL_IDM_CHANNEL, "/dev/hvc1") - .await - .map_err(|x| anyhow!("failed to open idm client: {}", x))?; - self.mount_config_image().await?; - - let config = self.parse_image_config().await?; - let launch = self.parse_launch_config().await?; - - self.mount_root_image(launch.root.format.clone()).await?; - - self.mount_addons().await?; - - self.mount_new_root().await?; - self.mount_kernel_modules().await?; - self.bind_new_root().await?; - - if let Some(hostname) = launch.hostname.clone() { - let result = unsafe { - sethostname( - hostname.as_bytes().as_ptr() as *mut libc::c_char, - hostname.len(), - ) - }; - if result != 0 { - warn!("failed to set hostname: {}", result); - } - - let etc = PathBuf::from_str("/etc")?; - if !etc.exists() { - fs::create_dir(&etc).await?; - } - let mut etc_hostname = etc; - etc_hostname.push("hostname"); - fs::write(&etc_hostname, hostname + "\n").await?; - } - - if let Some(network) = &launch.network { - trace!("initializing network"); - if let Err(error) = self.network_setup(&launch, network).await { - warn!("failed to initialize network: {}", error); - } - } - - if let Some(cfg) = config.config() { - trace!("running zone task"); - self.run(cfg, &launch, idm).await?; - } else { - return Err(anyhow!( - "unable to determine what to execute, image config doesn't tell us" - )); - } - Ok(()) - } - - async fn early_init(&mut self) -> Result<()> { - trace!("early init"); - self.create_dir("/dev", Some(0o0755)).await?; - self.create_dir("/proc", None).await?; - self.create_dir("/sys", Some(0o0555)).await?; - self.create_dir("/root", Some(0o0700)).await?; - self.create_dir("/tmp", None).await?; - self.create_dir("/run", Some(0o0755)).await?; - self.mount_kernel_fs("devtmpfs", "/dev", "mode=0755", None, None) - .await?; - self.mount_kernel_fs("proc", "/proc", "hidepid=1", None, None) - .await?; - self.mount_kernel_fs("sysfs", "/sys", "", None, None) - .await?; - self.create_dir("/dev/pts", Some(0o0755)).await?; - self.mount_kernel_fs("devpts", "/dev/pts", "", None, Some("/dev/ptmx")) - .await?; - fs::symlink("/proc/self/fd", "/dev/fd").await?; - fs::symlink("/proc/self/fd/0", "/dev/stdin").await?; - fs::symlink("/proc/self/fd/1", "/dev/stdout").await?; - fs::symlink("/proc/self/fd/2", "/dev/stderr").await?; - self.mount_kernel_fs( - "cgroup2", - "/sys/fs/cgroup", - "", - Some(MountFlags::RELATIME), - None, - ) - .await?; - Ok(()) - } - - async fn mount_addons(&mut self) -> Result<()> { - if !fs::try_exists(ADDONS_DEVICE_PATH).await? { - return Ok(()); - } - - self.mount_image( - &PathBuf::from(ADDONS_DEVICE_PATH), - &PathBuf::from(ADDONS_MOUNT_PATH), - LaunchPackedFormat::Squashfs, - ) - .await?; - Ok(()) - } - - async fn mount_kernel_modules(&mut self) -> Result<()> { - if !fs::try_exists(ADDONS_MODULES_PATH).await? { - return Ok(()); - } - - let Some(platform_info) = PlatformInfo::new().ok() else { - return Ok(()); - }; - - let kernel_release = platform_info.release().to_string_lossy().to_string(); - let modules_path = format!("/newroot/lib/modules/{}", kernel_release); - fs::create_dir_all(&modules_path).await?; - Mount::builder() - .fstype(FilesystemType::Manual("none")) - .flags(MountFlags::BIND | MountFlags::RDONLY) - .mount(ADDONS_MODULES_PATH, modules_path)?; - Ok(()) - } - - async fn create_dir(&mut self, path: &str, mode: Option) -> Result<()> { - let path = Path::new(path); - if !path.is_dir() { - trace!("creating directory {:?}", path); - fs::create_dir(path).await?; - } - if let Some(mode) = mode { - let permissions = Permissions::from_mode(mode); - trace!("setting directory {:?} permissions to {:?}", path, mode); - fs::set_permissions(path, permissions).await?; - } - Ok(()) - } - - async fn mount_kernel_fs( - &mut self, - fstype: &str, - path: &str, - data: &str, - flags: Option, - source: Option<&str>, - ) -> Result<()> { - trace!("mounting kernel fs {} to {}", fstype, path); - Mount::builder() - .fstype(FilesystemType::Manual(fstype)) - .flags(MountFlags::NOEXEC | MountFlags::NOSUID | flags.unwrap_or(MountFlags::empty())) - .data(data) - .mount(source.unwrap_or(fstype), path)?; - Ok(()) - } - - fn map_console(&mut self, console: &File) -> Result<()> { - trace!("mapping console"); - dup2(console.as_raw_fd(), 0)?; - dup2(console.as_raw_fd(), 1)?; - dup2(console.as_raw_fd(), 2)?; - Ok(()) - } - - async fn mount_config_image(&mut self) -> Result<()> { - trace!("mounting config image"); - let config_mount_path = Path::new(CONFIG_MOUNT_PATH); - self.mount_image( - Path::new(CONFIG_BLOCK_DEVICE_PATH), - config_mount_path, - LaunchPackedFormat::Squashfs, - ) - .await?; - Ok(()) - } - - async fn mount_root_image(&mut self, format: LaunchPackedFormat) -> Result<()> { - trace!("mounting root image"); - let image_mount_path = Path::new(IMAGE_MOUNT_PATH); - self.mount_image(Path::new(IMAGE_BLOCK_DEVICE_PATH), image_mount_path, format) - .await?; - Ok(()) - } - - async fn mount_image( - &mut self, - from: &Path, - to: &Path, - format: LaunchPackedFormat, - ) -> Result<()> { - trace!("mounting {:?} image {:?} to {:?}", format, from, to); - if !to.is_dir() { - fs::create_dir(to).await?; - } - Mount::builder() - .fstype(FilesystemType::Manual(match format { - LaunchPackedFormat::Squashfs => "squashfs", - LaunchPackedFormat::Erofs => "erofs", - })) - .flags(MountFlags::RDONLY) - .mount(from, to)?; - Ok(()) - } - - async fn mount_move_subtree(&mut self, from: &Path, to: &Path) -> Result<()> { - trace!("moving subtree {:?} to {:?}", from, to); - if !to.is_dir() { - fs::create_dir(to).await?; - } - Mount::builder() - .fstype(FilesystemType::Manual("none")) - .flags(MountFlags::MOVE) - .mount(from, to)?; - Ok(()) - } - - async fn mount_new_root(&mut self) -> Result<()> { - trace!("mounting new root"); - self.mount_overlay_tmpfs().await?; - self.bind_image_to_overlay_tmpfs().await?; - self.mount_overlay_to_new_root().await?; - std::env::set_current_dir(NEW_ROOT_PATH)?; - trace!("mounted new root"); - Ok(()) - } - - async fn mount_overlay_tmpfs(&mut self) -> Result<()> { - fs::create_dir(OVERLAY_MOUNT_PATH).await?; - Mount::builder() - .fstype(FilesystemType::Manual("tmpfs")) - .mount("tmpfs", OVERLAY_MOUNT_PATH)?; - fs::create_dir(OVERLAY_UPPER_PATH).await?; - fs::create_dir(OVERLAY_WORK_PATH).await?; - Ok(()) - } - - async fn bind_image_to_overlay_tmpfs(&mut self) -> Result<()> { - fs::create_dir(OVERLAY_IMAGE_BIND_PATH).await?; - Mount::builder() - .fstype(FilesystemType::Manual("none")) - .flags(MountFlags::BIND | MountFlags::RDONLY) - .mount(IMAGE_MOUNT_PATH, OVERLAY_IMAGE_BIND_PATH)?; - Ok(()) - } - - async fn mount_overlay_to_new_root(&mut self) -> Result<()> { - fs::create_dir(NEW_ROOT_PATH).await?; - Mount::builder() - .fstype(FilesystemType::Manual("overlay")) - .flags(MountFlags::NOATIME) - .data(&format!( - "lowerdir={},upperdir={},workdir={}", - OVERLAY_IMAGE_BIND_PATH, OVERLAY_UPPER_PATH, OVERLAY_WORK_PATH - )) - .mount(format!("overlayfs:{}", OVERLAY_MOUNT_PATH), NEW_ROOT_PATH)?; - Ok(()) - } - - async fn parse_image_config(&mut self) -> Result { - let image_config_path = Path::new(IMAGE_CONFIG_JSON_PATH); - let content = fs::read_to_string(image_config_path).await?; - let config = serde_json::from_str(&content)?; - Ok(config) - } - - async fn parse_launch_config(&mut self) -> Result { - trace!("parsing launch config"); - let launch_config = Path::new(LAUNCH_CONFIG_JSON_PATH); - let content = fs::read_to_string(launch_config).await?; - Ok(serde_json::from_str(&content)?) - } - - async fn bind_new_root(&mut self) -> Result<()> { - self.mount_move_subtree(Path::new(SYS_PATH), Path::new(NEW_ROOT_SYS_PATH)) - .await?; - self.mount_move_subtree(Path::new(PROC_PATH), Path::new(NEW_ROOT_PROC_PATH)) - .await?; - self.mount_move_subtree(Path::new(DEV_PATH), Path::new(NEW_ROOT_DEV_PATH)) - .await?; - trace!("binding new root"); - Mount::builder() - .fstype(FilesystemType::Manual("none")) - .flags(MountFlags::BIND) - .mount(".", "/")?; - trace!("chrooting into new root"); - chroot(".")?; - trace!("setting root as current directory"); - std::env::set_current_dir("/")?; - Ok(()) - } - - async fn network_setup(&mut self, cfg: &LaunchInfo, network: &LaunchNetwork) -> Result<()> { - trace!("setting up network for link"); - - let etc = PathBuf::from_str("/etc")?; - if !etc.exists() { - fs::create_dir(etc).await?; - } - let resolv = PathBuf::from_str("/etc/resolv.conf")?; - - { - let mut lines = vec!["# krata resolver configuration".to_string()]; - for nameserver in &network.resolver.nameservers { - lines.push(format!("nameserver {}", nameserver)); - } - - let mut conf = lines.join("\n"); - conf.push('\n'); - fs::write(resolv, conf).await?; - } - - let hosts = PathBuf::from_str("/etc/hosts")?; - if let Some(ref hostname) = cfg.hostname { - let mut lines = if hosts.exists() { - fs::read_to_string(&hosts) - .await? - .lines() - .map(|x| x.to_string()) - .collect::>() - } else { - vec!["127.0.0.1 localhost".to_string()] - }; - lines.push(format!("127.0.1.1 {}", hostname)); - fs::write(&hosts, lines.join("\n") + "\n").await?; - } - - self.network_configure_ethtool(network).await?; - self.network_configure_link(network).await?; - Ok(()) - } - - async fn network_configure_link(&mut self, network: &LaunchNetwork) -> Result<()> { - let (connection, handle, _) = rtnetlink::new_connection()?; - tokio::spawn(connection); - - let mut links = handle.link().get().match_name("lo".to_string()).execute(); - let Some(link) = links.try_next().await? else { - warn!("unable to find link named lo"); - return Ok(()); - }; - - handle.link().set(link.header.index).up().execute().await?; - - let ipv4_network: IpNetwork = network.ipv4.address.parse()?; - let ipv4_gateway: Ipv4Addr = network.ipv4.gateway.parse()?; - let ipv6_network: IpNetwork = network.ipv6.address.parse()?; - let ipv6_gateway: Ipv6Addr = network.ipv6.gateway.parse()?; - - let mut links = handle - .link() - .get() - .match_name(network.link.clone()) - .execute(); - let Some(link) = links.try_next().await? else { - warn!("unable to find link named {}", network.link); - return Ok(()); - }; - - handle - .address() - .add(link.header.index, ipv4_network.ip(), ipv4_network.prefix()) - .execute() - .await?; - - let ipv6_result = handle - .address() - .add(link.header.index, ipv6_network.ip(), ipv6_network.prefix()) - .execute() - .await; - - let ipv6_ready = match ipv6_result { - Ok(()) => true, - Err(error) => { - warn!("unable to setup ipv6 network: {}", error); - false - } - }; - - handle.link().set(link.header.index).up().execute().await?; - - handle - .route() - .add() - .v4() - .destination_prefix(Ipv4Addr::UNSPECIFIED, 0) - .output_interface(link.header.index) - .gateway(ipv4_gateway) - .execute() - .await?; - - if ipv6_ready { - let ipv6_gw_result = handle - .route() - .add() - .v6() - .destination_prefix(Ipv6Addr::UNSPECIFIED, 0) - .output_interface(link.header.index) - .gateway(ipv6_gateway) - .execute() - .await; - - if let Err(error) = ipv6_gw_result { - warn!("failed to add ipv6 gateway route: {}", error); - } - } - Ok(()) - } - - async fn network_configure_ethtool(&mut self, network: &LaunchNetwork) -> Result<()> { - let mut handle = EthtoolHandle::new()?; - handle.set_gso(&network.link, false)?; - handle.set_tso(&network.link, false)?; - Ok(()) - } - - async fn run( - &mut self, - config: &Config, - launch: &LaunchInfo, - idm: IdmInternalClient, - ) -> Result<()> { - let mut cmd = match config.cmd() { - None => vec![], - Some(value) => value.clone(), - }; - - if launch.run.is_some() { - cmd.clone_from(launch.run.as_ref().unwrap()); - } - - if let Some(entrypoint) = config.entrypoint() { - for item in entrypoint.iter().rev() { - cmd.insert(0, item.to_string()); - } - } - - if cmd.is_empty() { - cmd.push("/bin/sh".to_string()); - } - - let path = cmd.remove(0); - - let mut env = HashMap::new(); - if let Some(config_env) = config.env() { - env.extend(ZoneInit::env_map(config_env)); - } - env.extend(launch.env.clone()); - env.insert("KRATA_CONTAINER".to_string(), "1".to_string()); - - // If we were not provided a terminal definition in our launch manifest, we - // default to xterm as most terminal emulators support the xterm control codes. - if !env.contains_key("TERM") { - env.insert("TERM".to_string(), "xterm".to_string()); - } - - let path = resolve_executable(&env, path.into())?; - let Some(file_name) = path.file_name() else { - return Err(anyhow!("cannot get file name of command path")); - }; - let Some(file_name) = file_name.to_str() else { - return Err(anyhow!("cannot get file name of command path as str")); - }; - cmd.insert(0, file_name.to_string()); - let env = ZoneInit::env_list(env); - - trace!("running zone command: {}", cmd.join(" ")); - - let path = CString::new(path.as_os_str().as_bytes())?; - let cmd = ZoneInit::strings_as_cstrings(cmd)?; - let env = ZoneInit::strings_as_cstrings(env)?; - let mut working_dir = config - .working_dir() - .as_ref() - .map(|x| x.to_string()) - .unwrap_or("/".to_string()); - - if working_dir.is_empty() { - working_dir = "/".to_string(); - } - - let cgroup = self.init_cgroup().await?; - self.fork_and_exec(idm, cgroup, working_dir, path, cmd, env) - .await?; - Ok(()) - } - - async fn init_cgroup(&self) -> Result { - trace!("initializing cgroup"); - let hierarchy = cgroups_rs::hierarchies::auto(); - let cgroup = Cgroup::new(hierarchy, "krata-zone-task")?; - cgroup.set_cgroup_type("threaded")?; - trace!("initialized cgroup"); - Ok(cgroup) - } - - fn strings_as_cstrings(values: Vec) -> Result> { - let mut results: Vec = vec![]; - for value in values { - results.push(CString::new(value.as_bytes().to_vec())?); - } - Ok(results) - } - - fn env_map(env: &[String]) -> HashMap { - let mut map = HashMap::::new(); - for item in env { - if let Some((key, value)) = item.split_once('=') { - map.insert(key.to_string(), value.to_string()); - } - } - map - } - - fn env_list(env: HashMap) -> Vec { - env.iter() - .map(|(key, value)| format!("{}={}", key, value)) - .collect::>() - } - - async fn fork_and_exec( - &mut self, - idm: IdmInternalClient, - cgroup: Cgroup, - working_dir: String, - path: CString, - cmd: Vec, - env: Vec, - ) -> Result<()> { - match unsafe { fork()? } { - ForkResult::Parent { child } => self.background(idm, cgroup, child).await, - ForkResult::Child => self.foreground(cgroup, working_dir, path, cmd, env).await, - } - } - - async fn foreground( - &mut self, - cgroup: Cgroup, - working_dir: String, - path: CString, - cmd: Vec, - env: Vec, - ) -> Result<()> { - ZoneInit::set_controlling_terminal()?; - std::env::set_current_dir(working_dir)?; - cgroup.add_task(CgroupPid::from(std::process::id() as u64))?; - execve(&path, &cmd, &env)?; - Ok(()) - } - - fn set_controlling_terminal() -> Result<()> { - unsafe { - setsid(); - set_controlling_terminal(io::stdin().as_raw_fd(), 0)?; - } - Ok(()) - } - - async fn background( - &mut self, - idm: IdmInternalClient, - cgroup: Cgroup, - executed: Pid, - ) -> Result<()> { - let mut background = ZoneBackground::new(idm, cgroup, executed).await?; - background.run().await?; - Ok(()) - } -} - -pub fn resolve_executable(env: &HashMap, path: PathBuf) -> Result { - if path.is_absolute() { - return Ok(path); - } - - if path.is_file() { - return Ok(path.absolutize()?.to_path_buf()); - } - - if let Some(path_var) = env.get("PATH") { - for item in path_var.split(':') { - let mut exe_path: PathBuf = item.into(); - exe_path.push(&path); - if exe_path.is_file() { - return Ok(exe_path); - } - } - } - Ok(path) -} diff --git a/crates/zone/src/lib.rs b/crates/zone/src/lib.rs deleted file mode 100644 index 035dc0c1..00000000 --- a/crates/zone/src/lib.rs +++ /dev/null @@ -1,22 +0,0 @@ -use std::{os::raw::c_int, time::Duration}; - -use anyhow::Result; -use tokio::time::sleep; -use xenstore::{XsdClient, XsdInterface}; - -pub mod background; -pub mod childwait; -pub mod exec; -pub mod init; -pub mod metrics; - -pub async fn death(code: c_int) -> Result<()> { - let store = XsdClient::open().await?; - store - .write_string("krata/zone/exit-code", &code.to_string()) - .await?; - drop(store); - loop { - sleep(Duration::from_secs(1)).await; - } -} diff --git a/crates/zone/src/metrics.rs b/crates/zone/src/metrics.rs deleted file mode 100644 index e539be76..00000000 --- a/crates/zone/src/metrics.rs +++ /dev/null @@ -1,122 +0,0 @@ -use std::{ops::Add, path::Path}; - -use anyhow::Result; -use krata::idm::internal::{MetricFormat, MetricNode}; -use sysinfo::{Process, ProcessesToUpdate}; - -pub struct MetricsCollector {} - -impl MetricsCollector { - pub fn new() -> Result { - Ok(MetricsCollector {}) - } - - pub fn collect(&self) -> Result { - let mut sysinfo = sysinfo::System::new(); - Ok(MetricNode::structural( - "zone", - vec![ - self.collect_system(&mut sysinfo)?, - self.collect_processes(&mut sysinfo)?, - ], - )) - } - - fn collect_system(&self, sysinfo: &mut sysinfo::System) -> Result { - sysinfo.refresh_memory(); - Ok(MetricNode::structural( - "system", - vec![MetricNode::structural( - "memory", - vec![ - MetricNode::value("total", sysinfo.total_memory(), MetricFormat::Bytes), - MetricNode::value("used", sysinfo.used_memory(), MetricFormat::Bytes), - MetricNode::value("free", sysinfo.free_memory(), MetricFormat::Bytes), - ], - )], - )) - } - - fn collect_processes(&self, sysinfo: &mut sysinfo::System) -> Result { - sysinfo.refresh_processes(ProcessesToUpdate::All); - let mut processes = Vec::new(); - let mut sysinfo_processes = sysinfo.processes().values().collect::>(); - sysinfo_processes.sort_by_key(|x| x.pid()); - for process in sysinfo_processes { - if process.thread_kind().is_some() { - continue; - } - processes.push(MetricsCollector::process_node(process)?); - } - Ok(MetricNode::structural("process", processes)) - } - - fn process_node(process: &Process) -> Result { - let mut metrics = vec![]; - - if let Some(parent) = process.parent() { - metrics.push(MetricNode::value( - "parent", - parent.as_u32() as u64, - MetricFormat::Integer, - )); - } - - if let Some(exe) = process.exe().and_then(path_as_str) { - metrics.push(MetricNode::raw_value("executable", exe)); - } - - if let Some(working_directory) = process.cwd().and_then(path_as_str) { - metrics.push(MetricNode::raw_value("cwd", working_directory)); - } - - let cmdline = process - .cmd() - .iter() - .map(|x| x.to_string_lossy().to_string()) - .collect::>(); - metrics.push(MetricNode::raw_value("cmdline", cmdline)); - metrics.push(MetricNode::structural( - "memory", - vec![ - MetricNode::value("resident", process.memory(), MetricFormat::Bytes), - MetricNode::value("virtual", process.virtual_memory(), MetricFormat::Bytes), - ], - )); - - metrics.push(MetricNode::value( - "lifetime", - process.run_time(), - MetricFormat::DurationSeconds, - )); - metrics.push(MetricNode::value( - "uid", - process.user_id().map(|x| (*x).add(0)).unwrap_or(0) as f64, - MetricFormat::Integer, - )); - metrics.push(MetricNode::value( - "gid", - process.group_id().map(|x| (*x).add(0)).unwrap_or(0) as f64, - MetricFormat::Integer, - )); - metrics.push(MetricNode::value( - "euid", - process - .effective_user_id() - .map(|x| (*x).add(0)) - .unwrap_or(0) as f64, - MetricFormat::Integer, - )); - metrics.push(MetricNode::value( - "egid", - process.effective_group_id().map(|x| x.add(0)).unwrap_or(0) as f64, - MetricFormat::Integer, - )); - - Ok(MetricNode::structural(process.pid().to_string(), metrics)) - } -} - -fn path_as_str(path: &Path) -> Option { - String::from_utf8(path.as_os_str().as_encoded_bytes().to_vec()).ok() -} diff --git a/doc/admin-guide/custom-kernels.md b/doc/admin-guide/custom-kernels.md deleted file mode 100644 index 9babf862..00000000 --- a/doc/admin-guide/custom-kernels.md +++ /dev/null @@ -1,131 +0,0 @@ -Custom Kernels in krata -======================= - -Krata supports using custom kernels instead of the default Edera-provided -kernel both on a system-wide and zone-wide basis. Krata also supports using -a custom host kernel, as long as it meets certain technical requirements. - -System-wide default kernel for zones ------------------------------------- - -The standard system-wide default kernel for zones is stored in -`/var/lib/krata/zone/kernel` which is the kernel image that should be -booted for the zone, and `/var/lib/krata/zone/addons.squashfs`, -which contains a set of kernel modules that should be mounted in the -zone. - -Zone-wide alternative kernels via OCI -------------------------------------- - -Krata also supports fetching alternative kernel images for use in zones -via OCI repositories. These kernel images are distributed like any other -OCI image, but are not intended to be directly executed by an OCI runtime. - -To select an alternative kernel, you can supply the `-k` option to the -`kratactl zone launch` command that specifies an OCI tag to pull the -alternative kernel image from. - -OCI-based kernel image contents -------------------------------- - -OCI-based kernel images contain the following files: - -* `/kernel/image`: The kernel image itself. - -* `/kernel/addons.squashfs`: A squashfs file containing the kernel - modules for a given kernel image. - -* `/kernel/metadata`: A file containing the following metadata fields - in `KEY=VALUE` format: - - `KERNEL_ARCH`: The kernel architecture (`x86_64` or `aarch64`) - - `KERNEL_VERSION`: The kernel version - - `KERNEL_FLAVOR`: The kernel flavor (examples: `standard`, `dom0` or `openpax`) - - `KERNEL_CONFIG`: The digest for the relevant configuration file stored in the OCI - repository - - `KERNEL_TAGS`: The OCI tags this kernel image was originally built for - (example: `latest`) - -Minimum requirements for a zone-wide/system-wide kernel -------------------------------------------------------- - -The following configuration options must be set: - -``` -CONFIG_XEN=y -CONFIG_XEN_PV=y -CONFIG_XEN_512GB=y -CONFIG_XEN_PV_SMP=y -CONFIG_XEN_PVHVM=y -CONFIG_XEN_PVHVM_SMP=y -CONFIG_XEN_PVHVM_GUEST=y -CONFIG_XEN_SAVE_RESTORE=y -CONFIG_XEN_PVH=y -CONFIG_XEN_PV_MSR_SAFE=y -CONFIG_PCI_XEN=y -CONFIG_NET_9P_XEN=y -CONFIG_XEN_PCIDEV_FRONTEND=y -CONFIG_XEN_BLKDEV_FRONTEND=y -CONFIG_XEN_NETDEV_FRONTEND=y -CONFIG_INPUT_XEN_KBDDEV_FRONTEND=y -CONFIG_HVC_XEN=y -CONFIG_HVC_XEN_FRONTEND=y -CONFIG_XEN_FBDEV_FRONTEND=m -CONFIG_XEN_BALLOON=y -CONFIG_XEN_BALLOON_MEMORY_HOTPLUG=y -CONFIG_XEN_MEMORY_HOTPLUG_LIMIT=512 -CONFIG_XEN_SCRUB_PAGES_DEFAULT=y -CONFIG_XEN_DEV_EVTCHN=y -CONFIG_XEN_BACKEND=y -CONFIG_XENFS=y -CONFIG_XEN_COMPAT_XENFS=y -CONFIG_XEN_SYS_HYPERVISOR=y -CONFIG_XEN_XENBUS_FRONTEND=y -CONFIG_SWIOTLB_XEN=y -CONFIG_XEN_HAVE_PVMMU=y -CONFIG_XEN_EFI=y -CONFIG_XEN_AUTO_XLATE=y -CONFIG_XEN_ACPI=y -CONFIG_XEN_HAVE_VPMU=y -CONFIG_XEN_GRANT_DMA_OPS=y -CONFIG_XEN_VIRTIO=y -``` - -It is possible to copy these options into a `.config` file and then use -`make olddefconfig` to build the rest of the kernel configuration, which -you can then use to build a kernel as desired. - -The [linux-kernel-oci][edera-linux-kernel-oci] repository provides some example configurations -and can generate a Dockerfile which will build a kernel image. - - [edera-linux-kernel-oci]: https://github.com/edera-dev/linux-kernel-oci - -Minimum requirements for a host kernel --------------------------------------- - -The configuration options above are also required for a host kernel. -In addition, the following options are also required: - -``` -CONFIG_XEN_PV_DOM0=y -CONFIG_XEN_DOM0=y -CONFIG_PCI_XEN=y -CONFIG_XEN_PCIDEV_BACKEND=y -CONFIG_XEN_BLKDEV_BACKEND=y -CONFIG_XEN_NETDEV_BACKEND=y -CONFIG_XEN_SCSI_BACKEND=y -CONFIG_XEN_PVCALLS_BACKEND=y -CONFIG_TCG_XEN=m -CONFIG_XEN_WDT=y -CONFIG_XEN_DEV_EVTCHN=y -CONFIG_XEN_GNTDEV=y -CONFIG_XEN_GRANT_DEV_ALLOC=y -CONFIG_XEN_GRANT_DMA_ALLOC=y -CONFIG_SWIOTLB_XEN=y -CONFIG_XEN_PRIVCMD=y -CONFIG_XEN_ACPI_PROCESSOR=y -CONFIG_XEN_MCE_LOG=y -``` - -Build and install the kernel as you normally would for your system. -Assuming GRUB is the bootloader, it will automatically detect the new -host kernel when you run `grub-mkconfig` or `grub2-mkconfig`. diff --git a/hack/ci/assemble-release-assets.sh b/hack/ci/assemble-release-assets.sh deleted file mode 100755 index 299cb067..00000000 --- a/hack/ci/assemble-release-assets.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/sh -set -e - -checksum_sha256() { - if type sha256sum > /dev/null 2>&1 - then - sha256sum "${1}" - else - shasum -a 256 "${1}" - fi -} - -asset() { - cp "${1}" "${2}" - PREVIOUS="${PWD}" - cd "$(dirname "${2}")" - BASE_FILE_NAME="$(basename "${2}")" - checksum_sha256 "${BASE_FILE_NAME}" > "${BASE_FILE_NAME}.sha256" - cd "${PREVIOUS}" -} - -FORM="${1}" -shift -TAG_NAME="${1}" -shift -PLATFORM="${1}" -shift - -mkdir -p target/assets - -for SOURCE_FILE_PATH in "${@}" -do - if [ "${FORM}" = "kratactl" ] - then - SUFFIX="" - if echo "${PLATFORM}" | grep "^windows-" > /dev/null - then - SUFFIX=".exe" - fi - asset "${SOURCE_FILE_PATH}" "target/assets/kratactl_${TAG_NAME}_${PLATFORM}${SUFFIX}" - elif [ "${FORM}" = "debian" ] - then - asset "${SOURCE_FILE_PATH}" "target/assets/krata_${TAG_NAME}_${PLATFORM}.deb" - elif [ "${FORM}" = "alpine" ] - then - asset "${SOURCE_FILE_PATH}" "target/assets/krata_${TAG_NAME}_${PLATFORM}.apk" - elif [ "${FORM}" = "bundle-systemd" ] - then - asset "${SOURCE_FILE_PATH}" "target/assets/krata-systemd_${TAG_NAME}_${PLATFORM}.tgz" - else - echo "ERROR: Unknown form '${FORM}'" - exit 1 - fi -done diff --git a/hack/ci/install-darwin-deps.sh b/hack/ci/install-darwin-deps.sh index 063a8474..a9d03c58 100755 --- a/hack/ci/install-darwin-deps.sh +++ b/hack/ci/install-darwin-deps.sh @@ -1,5 +1,4 @@ #!/bin/sh set -e -brew install protobuf brew upgrade rustup || true diff --git a/hack/ci/install-linux-deps.sh b/hack/ci/install-linux-deps.sh index 540b0418..a9152f59 100755 --- a/hack/ci/install-linux-deps.sh +++ b/hack/ci/install-linux-deps.sh @@ -2,9 +2,8 @@ set -e CROSS_RS_REV="7b79041c9278769eca57fae10c74741f5aa5c14b" -FPM_VERSION="1.15.1" -PACKAGES=(build-essential musl-dev protobuf-compiler musl-tools) +PACKAGES=(build-essential musl-dev musl-tools) sudo apt-get update @@ -21,8 +20,3 @@ if [ "${CROSS_COMPILE}" = "1" ] then cargo install cross --git "https://github.com/cross-rs/cross.git" --rev "${CROSS_RS_REV}" fi - -if [ "${CI_NEEDS_FPM}" = "1" ] -then - sudo gem install --no-document fpm -v "${FPM_VERSION}" -fi diff --git a/hack/ci/install-windows-deps.sh b/hack/ci/install-windows-deps.sh index bddd72f8..d37118bb 100755 --- a/hack/ci/install-windows-deps.sh +++ b/hack/ci/install-windows-deps.sh @@ -1,4 +1,2 @@ #!/bin/sh set -e - -choco install protoc diff --git a/hack/ci/upload-release-assets.sh b/hack/ci/upload-release-assets.sh deleted file mode 100755 index 6d33d033..00000000 --- a/hack/ci/upload-release-assets.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh -set -e - -retry() { - for i in $(seq 1 10) - do - if "${@}" - then - return 0 - else - sleep "${i}" - fi - done - "${@}" -} - -TAG="${1}" -shift - -cd target/assets - -retry gh release upload "${TAG}" --clobber ./* diff --git a/hack/debug/common.sh b/hack/debug/common.sh deleted file mode 100644 index 43ac157c..00000000 --- a/hack/debug/common.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh -set -e - -REAL_SCRIPT="$(realpath "${0}")" -cd "$(dirname "${REAL_SCRIPT}")/../.." - -if [ -z "${RUST_LOG}" ] -then - RUST_LOG="INFO" -fi - -CARGO_BUILD_FLAGS="" - -if [ "${KRATA_BUILD_QUIET}" = "1" ] -then - CARGO_BUILD_FLAGS="-q" -fi - -build_and_run() { - EXE_TARGET="${1}" - shift - sudo mkdir -p /var/lib/krata/zone - if [ "${KRATA_BUILD_INITRD}" = "1" ] - then - TARGET_ARCH="$(./hack/build/arch.sh)" - ./hack/initrd/build.sh ${CARGO_BUILD_FLAGS} - sudo cp "target/initrd/initrd-${TARGET_ARCH}" "/var/lib/krata/zone/initrd" - fi - RUST_TARGET="$(./hack/build/target.sh)" - ./hack/build/cargo.sh build ${CARGO_BUILD_FLAGS} --bin "${EXE_TARGET}" - exec sudo -E sh -c "RUST_LOG='${RUST_LOG}' 'target/${RUST_TARGET}/debug/${EXE_TARGET}' $*" -} diff --git a/hack/debug/kratactl.sh b/hack/debug/kratactl.sh deleted file mode 100755 index d0dd7f45..00000000 --- a/hack/debug/kratactl.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -set -e - -REAL_SCRIPT="$(realpath "${0}")" -DEBUG_DIR="$(dirname "${REAL_SCRIPT}")" -# shellcheck source-path=SCRIPTDIR source=common.sh -. "${DEBUG_DIR}/common.sh" - -build_and_run kratactl "${@}" diff --git a/hack/debug/kratad.sh b/hack/debug/kratad.sh deleted file mode 100755 index 8b94fe66..00000000 --- a/hack/debug/kratad.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -set -e - -REAL_SCRIPT="$(realpath "${0}")" -DEBUG_DIR="$(dirname "${REAL_SCRIPT}")" -# shellcheck source-path=SCRIPTDIR source=common.sh -. "${DEBUG_DIR}/common.sh" - -KRATA_BUILD_INITRD=1 build_and_run kratad "${@}" diff --git a/hack/debug/kratanet.sh b/hack/debug/kratanet.sh deleted file mode 100755 index 3fdb6216..00000000 --- a/hack/debug/kratanet.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -set -e - -REAL_SCRIPT="$(realpath "${0}")" -DEBUG_DIR="$(dirname "${REAL_SCRIPT}")" -# shellcheck source-path=SCRIPTDIR source=common.sh -. "${DEBUG_DIR}/common.sh" - -build_and_run kratanet "${@}" diff --git a/hack/debug/session.sh b/hack/debug/session.sh deleted file mode 100755 index 4eb2be46..00000000 --- a/hack/debug/session.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -set -e - -stop_service_if_running() { - if sudo systemctl is-active "${1}" > /dev/null 2>&1 - then - sudo systemctl stop "${1}" - fi - -} - -stop_service_if_running "kratad.service" -stop_service_if_running "kratanet.service" -tmuxp load "$(dirname "${0}")/session.yml" diff --git a/hack/debug/session.yml b/hack/debug/session.yml deleted file mode 100644 index e026bf60..00000000 --- a/hack/debug/session.yml +++ /dev/null @@ -1,11 +0,0 @@ -session_name: krata-dev -start_directory: ../.. -sleep_after: 3 -windows: -- window_name: live - layout: tiled - panes: - - shell_command: ./hack/debug/kratad.sh - - shell_command: ./hack/debug/kratanet.sh - - focus: true - shell_command: "alias kratactl=./hack/debug/kratactl.sh" diff --git a/hack/dist/apk.sh b/hack/dist/apk.sh deleted file mode 100755 index fd3f37b9..00000000 --- a/hack/dist/apk.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh -set -e - -# shellcheck source-path=SCRIPTDIR source=common.sh -. "$(dirname "${0}")/common.sh" - -export TARGET_LIBC="musl" -KRATA_SYSTAR_OPENRC=1 "${KRATA_DIR}/hack/dist/systar.sh" - -KRATA_VERSION="$("${KRATA_DIR}/hack/dist/version.sh")" -TARGET_ARCH="$("${KRATA_DIR}/hack/build/arch.sh")" - -cd "${OUTPUT_DIR}" - -rm -f "krata_${KRATA_VERSION}_${TARGET_ARCH}.apk" - -fpm -s tar -t apk \ - --name krata \ - --license agpl3 \ - --version "${KRATA_VERSION}" \ - --architecture "${TARGET_ARCH}" \ - --depends "squashfs-tools" \ - --depends "erofs-utils" \ - --description "Krata Isolation Engine" \ - --url "https://krata.dev" \ - --maintainer "Edera Team " \ - "${OUTPUT_DIR}/system-openrc-${TARGET_ARCH}.tgz" diff --git a/hack/dist/bundle.sh b/hack/dist/bundle.sh deleted file mode 100755 index 49889199..00000000 --- a/hack/dist/bundle.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh -set -e - -# shellcheck source-path=SCRIPTDIR source=common.sh -. "$(dirname "${0}")/common.sh" - -TARGET_ARCH="$("${KRATA_DIR}/hack/build/arch.sh")" -BUNDLE_TAR="${OUTPUT_DIR}/bundle-systemd-${TARGET_ARCH}.tgz" -rm -f "${BUNDLE_TAR}" -BUNDLE_DIR="$(mktemp -d /tmp/krata-bundle.XXXXXXXXXXXXX)" -BUNDLE_DIR="${BUNDLE_DIR}/krata" -mkdir -p "${BUNDLE_DIR}" - -./hack/build/cargo.sh build --release --bin kratad --bin kratanet --bin kratactl - -RUST_TARGET="$(./hack/build/target.sh)" -for X in kratad kratanet kratactl -do - cp "${KRATA_DIR}/target/${RUST_TARGET}/release/${X}" "${BUNDLE_DIR}/${X}" -done -./hack/initrd/build.sh -./hack/kernel/fetch.sh - -cd "${BUNDLE_DIR}" - -cp "${KRATA_DIR}/target/initrd/initrd-${TARGET_ARCH}" initrd -cp "${KRATA_DIR}/target/kernel/kernel-${TARGET_ARCH}" kernel -cp "${KRATA_DIR}/target/kernel/addons-${TARGET_ARCH}.squashfs" addons.squashfs -cp "${KRATA_DIR}/resources/systemd/kratad.service" kratad.service -cp "${KRATA_DIR}/resources/systemd/kratanet.service" kratanet.service -cp "${KRATA_DIR}/resources/bundle/install.sh" install.sh -cp "${KRATA_DIR}/resources/bundle/uninstall.sh" uninstall.sh - -for X in install.sh uninstall.sh kratactl kratad kratanet -do - chmod +x "${X}" -done - -cd .. -tar czf "${BUNDLE_TAR}" . -cd "${KRATA_DIR}" -rm -rf "$(dirname "${BUNDLE_DIR}")" diff --git a/hack/dist/deb.sh b/hack/dist/deb.sh deleted file mode 100755 index cfa1065a..00000000 --- a/hack/dist/deb.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh -set -e - -# shellcheck source-path=SCRIPTDIR source=common.sh -. "$(dirname "${0}")/common.sh" - -"${KRATA_DIR}/hack/dist/systar.sh" - -KRATA_VERSION="$("${KRATA_DIR}/hack/dist/version.sh")" -TARGET_ARCH_STANDARD="$(KRATA_ARCH_ALT_NAME=0 "${KRATA_DIR}/hack/build/arch.sh")" -TARGET_ARCH_DEBIAN="$(KRATA_ARCH_ALT_NAME=1 "${KRATA_DIR}/hack/build/arch.sh")" - -cd "${OUTPUT_DIR}" - -rm -f "krata_${KRATA_VERSION}_${TARGET_ARCH_DEBIAN}.deb" - -fpm -s tar -t deb \ - --name krata \ - --license agpl3 \ - --version "${KRATA_VERSION}" \ - --architecture "${TARGET_ARCH_DEBIAN}" \ - --depends "xen-system-${TARGET_ARCH_DEBIAN}" \ - --depends "squashfs-tools" \ - --depends "erofs-utils" \ - --description "Krata Isolation Engine" \ - --url "https://krata.dev" \ - --maintainer "Edera Team " \ - -x "usr/lib/**" \ - --deb-systemd "${KRATA_DIR}/resources/systemd/kratad.service" \ - --deb-systemd "${KRATA_DIR}/resources/systemd/kratanet.service" \ - --deb-systemd-enable \ - --deb-systemd-auto-start \ - "${OUTPUT_DIR}/system-systemd-${TARGET_ARCH_STANDARD}.tgz" diff --git a/hack/dist/systar.sh b/hack/dist/systar.sh deleted file mode 100755 index c1672aab..00000000 --- a/hack/dist/systar.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/sh -set -e - -# shellcheck source-path=SCRIPTDIR source=common.sh -. "$(dirname "${0}")/common.sh" - -"${KRATA_DIR}/hack/dist/bundle.sh" - -SYSTAR_VARIANT="systemd" -if [ "${KRATA_SYSTAR_OPENRC}" = "1" ] -then - SYSTAR_VARIANT="openrc" -fi - -TARGET_ARCH="$("${KRATA_DIR}/hack/build/arch.sh")" -SYSTAR="${OUTPUT_DIR}/system-${SYSTAR_VARIANT}-${TARGET_ARCH}.tgz" -rm -f "${SYSTAR}" -SYSTAR_DIR="$(mktemp -d /tmp/krata-systar.XXXXXXXXXXXXX)" -cd "${SYSTAR_DIR}" -tar xf "${OUTPUT_DIR}/bundle-systemd-${TARGET_ARCH}.tgz" - -mkdir sys -cd sys - -mkdir -p usr/bin usr/sbin -mv ../krata/kratactl usr/bin -mv ../krata/kratanet ../krata/kratad usr/sbin/ - -if [ "${SYSTAR_VARIANT}" = "openrc" ] -then - mkdir -p etc/init.d - cp "${KRATA_DIR}/resources/openrc/kratad" etc/init.d/kratad - cp "${KRATA_DIR}/resources/openrc/kratanet" etc/init.d/kratanet - chmod +x etc/init.d/kratad - chmod +x etc/init.d/kratanet -else - mkdir -p usr/lib/systemd/system - mv ../krata/kratad.service ../krata/kratanet.service usr/lib/systemd/system/ -fi - -mkdir -p usr/share/krata/zone -mv ../krata/kernel ../krata/initrd usr/share/krata/zone -mv ../krata/addons.squashfs usr/share/krata/zone/addons.squashfs - -tar czf "${SYSTAR}" --owner 0 --group 0 . - -cd "${KRATA_DIR}" -rm -rf "${SYSTAR_DIR}" diff --git a/hack/initrd/build.sh b/hack/initrd/build.sh deleted file mode 100755 index 585accef..00000000 --- a/hack/initrd/build.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh -set -e - -REAL_SCRIPT="$(realpath "${0}")" -cd "$(dirname "${REAL_SCRIPT}")/../.." -KRATA_DIR="${PWD}" -cd "${KRATA_DIR}" - -TARGET_ARCH="$(./hack/build/arch.sh)" - -export TARGET_LIBC="musl" -RUST_TARGET="$(./hack/build/target.sh)" -export RUSTFLAGS="-Ctarget-feature=+crt-static" - -./hack/build/cargo.sh build "${@}" --release --bin krata-zone -INITRD_DIR="$(mktemp -d /tmp/krata-initrd.XXXXXXXXXXXXX)" -cp "target/${RUST_TARGET}/release/krata-zone" "${INITRD_DIR}/init" -chmod +x "${INITRD_DIR}/init" -cd "${INITRD_DIR}" -mkdir -p "${KRATA_DIR}/target/initrd" -find . | cpio -R 0:0 --ignore-devno --renumber-inodes -o -H newc --quiet > "${KRATA_DIR}/target/initrd/initrd-${TARGET_ARCH}" -rm -rf "${INITRD_DIR}" diff --git a/hack/kernel/fetch.sh b/hack/kernel/fetch.sh deleted file mode 100755 index 474f615a..00000000 --- a/hack/kernel/fetch.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -set -e - -REAL_SCRIPT="$(realpath "${0}")" -cd "$(dirname "${REAL_SCRIPT}")/../.." -KRATA_DIR="${PWD}" -cd "${KRATA_DIR}" - -HOST_RUST_TARGET="$(TARGET_ARCH="" TARGET_LIBC="" ./hack/build/target.sh)" -TARGET_ARCH="$(./hack/build/arch.sh)" - -if [ "${1}" != "-u" ] && [ -f "target/kernel/kernel-${TARGET_ARCH}" ] -then - exit 0 -fi - -export TARGET_ARCH -TARGET_ARCH="" TARGET_LIBC="" RUST_TARGET="${HOST_RUST_TARGET}" ./hack/build/cargo.sh build -q --bin build-fetch-kernel -exec "target/${HOST_RUST_TARGET}/debug/build-fetch-kernel" "ghcr.io/edera-dev/linux-kernel:latest" diff --git a/images/Dockerfile.krata-zone b/images/Dockerfile.krata-zone deleted file mode 100644 index c1826afb..00000000 --- a/images/Dockerfile.krata-zone +++ /dev/null @@ -1,10 +0,0 @@ -FROM rust:1.80-alpine@sha256:1f5aff501e02c1384ec61bb47f89e3eebf60e287e6ed5d1c598077afc82e83d5 AS build -RUN apk update && apk add protoc protobuf-dev build-base && rm -rf /var/cache/apk/* -ENV TARGET_LIBC=musl TARGET_VENDOR=unknown - -WORKDIR /usr/src/app -COPY . . -RUN ./hack/initrd/build.sh && cp target/initrd/initrd-* target/initrd/initrd - -FROM scratch AS final -COPY --from=build /usr/src/app/target/initrd/initrd /krata/initrd diff --git a/images/Dockerfile.kratactl b/images/Dockerfile.kratactl deleted file mode 100644 index 3994f967..00000000 --- a/images/Dockerfile.kratactl +++ /dev/null @@ -1,12 +0,0 @@ -FROM rust:1.80-alpine@sha256:1f5aff501e02c1384ec61bb47f89e3eebf60e287e6ed5d1c598077afc82e83d5 AS build -RUN apk update && apk add protoc protobuf-dev build-base && rm -rf /var/cache/apk/* -ENV TARGET_LIBC=musl TARGET_VENDOR=unknown - -WORKDIR /usr/src/app -COPY . . -RUN ./hack/build/cargo.sh build --release --bin kratactl -RUN mv ./target/$(./hack/build/target.sh)/release/kratactl /usr/sbin - -FROM scratch -ENTRYPOINT ["/usr/sbin/kratactl"] -COPY --from=build /usr/sbin/kratactl /usr/sbin/kratactl diff --git a/images/Dockerfile.kratad b/images/Dockerfile.kratad deleted file mode 100644 index 756afc4a..00000000 --- a/images/Dockerfile.kratad +++ /dev/null @@ -1,13 +0,0 @@ -FROM rust:1.80-alpine@sha256:1f5aff501e02c1384ec61bb47f89e3eebf60e287e6ed5d1c598077afc82e83d5 AS build -RUN apk update && apk add protoc protobuf-dev build-base && rm -rf /var/cache/apk/* -ENV TARGET_LIBC=musl TARGET_VENDOR=unknown - -WORKDIR /usr/src/app -COPY . . -RUN ./hack/build/cargo.sh build --release --bin kratad -RUN mv ./target/$(./hack/build/target.sh)/release/kratad /usr/sbin - -FROM scratch -ENTRYPOINT ["/usr/sbin/kratad"] -COPY --from=build /usr/sbin/kratad /usr/sbin/kratad -COPY ./resources/systemd/kratad.service /usr/lib/systemd/system/kratad.service diff --git a/images/Dockerfile.kratanet b/images/Dockerfile.kratanet deleted file mode 100644 index 284ccd43..00000000 --- a/images/Dockerfile.kratanet +++ /dev/null @@ -1,13 +0,0 @@ -FROM rust:1.80-alpine@sha256:1f5aff501e02c1384ec61bb47f89e3eebf60e287e6ed5d1c598077afc82e83d5 AS build -RUN apk update && apk add protoc protobuf-dev build-base && rm -rf /var/cache/apk/* -ENV TARGET_LIBC=musl TARGET_VENDOR=unknown - -WORKDIR /usr/src/app -COPY . . -RUN ./hack/build/cargo.sh build --release --bin kratanet -RUN mv ./target/$(./hack/build/target.sh)/release/kratanet /usr/sbin - -FROM scratch -ENTRYPOINT ["/usr/sbin/kratanet"] -COPY --from=build /usr/sbin/kratanet /usr/sbin/kratanet -COPY ./resources/systemd/kratanet.service /usr/lib/systemd/system/kratanet.service diff --git a/lefthook.toml b/lefthook.toml index 35af79f2..4ebc883b 100644 --- a/lefthook.toml +++ b/lefthook.toml @@ -13,8 +13,5 @@ run = "./hack/build/cargo.sh clippy" [pre-commit.commands.fmt] run = "./hack/build/cargo.sh fmt --all -- --check" -[pre-commit.commands.initrd] -run = "./hack/initrd/build.sh" - [pre-commit.commands.shellcheck] run = "./hack/code/shellcheck.sh" diff --git a/release-plz.toml b/release-plz.toml index 2260caae..9b8f69dc 100644 --- a/release-plz.toml +++ b/release-plz.toml @@ -4,7 +4,7 @@ git_tag_enable = false changelog_update = false [[package]] -name = "krata" +name = "krata-runtime" git_release_name = "v{{ version }}" git_tag_name = "v{{ version }}" git_tag_enable = true @@ -12,34 +12,11 @@ git_release_enable = true changelog_update = true changelog_path = "./CHANGELOG.md" changelog_include = [ - "krata-daemon", - "krata-ctl", - "krata-zone", - "krata-network", "krata-runtime", - "krata-oci", + "krata-xencall", + "krata-xenclient", + "krata-xenevtchn", + "krata-xengnt", + "krata-xenplatform", + "krata-xenstore", ] - -[[package]] -name = "krata-xencall" -semver_check = false - -[[package]] -name = "krata-xenclient" -semver_check = false - -[[package]] -name = "krata-xenevtchn" -semver_check = false - -[[package]] -name = "krata-xengnt" -semver_check = false - -[[package]] -name = "krata-xenplatform" -semver_check = false - -[[package]] -name = "krata-xenstore" -semver_check = false diff --git a/resources/bundle/install.sh b/resources/bundle/install.sh deleted file mode 100755 index 92bc5065..00000000 --- a/resources/bundle/install.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/sh -set -e - -remove_service_if_exists() { - if systemctl show -P FragmentPath "${1}" > /dev/null - then - UNIT_PATH="$(systemctl show -P FragmentPath "${1}")" - if [ -f "${UNIT_PATH}" ] - then - echo "[WARN] disabling and removing systemd unit ${UNIT_PATH}" > /dev/stderr - systemctl disable --now "${1}" || true - rm "${UNIT_PATH}" - fi - fi -} - -REAL_SCRIPT="$(realpath "${0}")" -cd "$(dirname "${REAL_SCRIPT}")" - -remove_service_if_exists kratad.service -remove_service_if_exists kratanet.service - -cp kratad.service /usr/lib/systemd/system/kratad.service -cp kratanet.service /usr/lib/systemd/system/kratanet.service - -cp kratad kratanet /usr/sbin -cp kratactl /usr/bin - -chmod +x /usr/sbin/kratad -chmod +x /usr/sbin/kratanet -chmod +x /usr/bin/kratactl - -mkdir -p /var/lib/krata /usr/share/krata/zone -cp kernel /usr/share/krata/zone/kernel -cp initrd /usr/share/krata/zone/initrd - -systemctl daemon-reload -systemctl enable kratad.service kratanet.service -systemctl restart kratad.service kratanet.service diff --git a/resources/bundle/uninstall.sh b/resources/bundle/uninstall.sh deleted file mode 100755 index 20774691..00000000 --- a/resources/bundle/uninstall.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh -set -e - -systemctl disable --now kratad.service || true -systemctl disable --now kratanet.service || true - -rm -f /usr/lib/systemd/system/kratad.service -rm -f /usr/lib/systemd/system/kratanet.service - -rm -f /usr/bin/kratactl -rm -f /usr/sbin/kratad /usr/sbin/kratanet -rm -rf /usr/share/krata diff --git a/resources/openrc/kratad b/resources/openrc/kratad deleted file mode 100644 index 9d2015c8..00000000 --- a/resources/openrc/kratad +++ /dev/null @@ -1,12 +0,0 @@ -#!/sbin/openrc-run -description="Krata Isolation Engine" -command="/usr/sbin/kratad" -supervisor="supervise-daemon" -output_log="/var/log/kratad.log" -error_log="/var/log/kratad.err" - -depend() { - use xenstored -} - -export RUST_LOG=info diff --git a/resources/openrc/kratanet b/resources/openrc/kratanet deleted file mode 100644 index 26f87ab1..00000000 --- a/resources/openrc/kratanet +++ /dev/null @@ -1,12 +0,0 @@ -#!/sbin/openrc-run -description="Krata Networking Daemon" -command="/usr/sbin/kratanet" -supervisor="supervise-daemon" -output_log="/var/log/kratanet.log" -error_log="/var/log/kratanet.err" - -depend() { - use kratad -} - -export RUST_LOG=info diff --git a/resources/systemd/kratad.service b/resources/systemd/kratad.service deleted file mode 100644 index b5d79b24..00000000 --- a/resources/systemd/kratad.service +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=Krata Isolation Engine - -[Service] -Restart=on-failure -Type=simple -ExecStart=/usr/sbin/kratad -l unix:///var/lib/krata/daemon.socket -Environment=RUST_LOG=info -User=root - -[Install] -WantedBy=multi-user.target diff --git a/resources/systemd/kratanet.service b/resources/systemd/kratanet.service deleted file mode 100644 index b9bb2e75..00000000 --- a/resources/systemd/kratanet.service +++ /dev/null @@ -1,14 +0,0 @@ -[Unit] -Description=Krata Networking Engine -Wants=kratad.service -After=kratad.service - -[Service] -Restart=on-failure -Type=simple -ExecStart=/usr/sbin/kratanet -Environment=RUST_LOG=info -User=root - -[Install] -WantedBy=multi-user.target