mirror of
https://github.com/edera-dev/krata.git
synced 2025-08-03 05:10:55 +00:00
Compare commits
149 Commits
Author | SHA1 | Date | |
---|---|---|---|
d469da4d9b | |||
99091df3cf | |||
08b30c2eaa | |||
224fdbe227 | |||
62569f6c59 | |||
0b991f454e | |||
75aba8a1e3 | |||
8216ab3602 | |||
902fffe207 | |||
45cfc6bb27 | |||
146bda0810 | |||
45e7d7515b | |||
f161b5afd6 | |||
7fe3e2c7cb | |||
3a5be71db4 | |||
d1b910f5c4 | |||
8806a79161 | |||
c8795fa08d | |||
d792eb5439 | |||
398e555bd3 | |||
75901233b1 | |||
04665ce690 | |||
481a5884d9 | |||
5ee1035896 | |||
9bd8d1bb1d | |||
3bada811b2 | |||
e08d25ebde | |||
2c884a6882 | |||
d756fa82f4 | |||
6e051f52b9 | |||
b2fba6400e | |||
b26469be28 | |||
28d63d7d70 | |||
6b91f0be94 | |||
9e91ffe065 | |||
b57d95c610 | |||
de6bfe38fe | |||
f6dffd6e17 | |||
07cceed0c8 | |||
4ef466ceb6 | |||
8c9b3a6ceb | |||
a970cddacf | |||
a878d16c3c | |||
1126f1ffc9 | |||
d1b2cb3683 | |||
8e1e197113 | |||
ffb7de7d68 | |||
bd464d9f03 | |||
31d04c2f43 | |||
04401c1d07 | |||
b2dd4af09b | |||
783dd51f05 | |||
2f866ad47b | |||
94e45c1c8c | |||
3a398810b6 | |||
5da214fa48 | |||
8840bf34a4 | |||
f953c87b90 | |||
ff571630b9 | |||
e45a9d82d2 | |||
98ca623828 | |||
deeaa20a4a | |||
fe8e1d5521 | |||
367d31b11f | |||
71301ee689 | |||
350e02c553 | |||
f0914fb39f | |||
0e64d4ea79 | |||
35d585e3b1 | |||
a79320b4fc | |||
39ded9c7f4 | |||
b42b730b77 | |||
0f49d0cec4 | |||
dc4b14b5d1 | |||
f5b4c66ec7 | |||
9062d78e51 | |||
6161bea7bf | |||
8363ed0085 | |||
8ddc190018 | |||
c687561541 | |||
4c83902729 | |||
6f50167798 | |||
88a62441b1 | |||
93aae83b3f | |||
6e1e4e3806 | |||
9e532345f0 | |||
89b7f40520 | |||
4175e1e3fe | |||
1bdf3bda87 | |||
9a45d754bf | |||
6c3fc54688 | |||
af6a1a3ad2 | |||
7bef74fadf | |||
ec1b6d4370 | |||
b2d146713b | |||
b730b08d6e | |||
87758d7ae9 | |||
349664abf1 | |||
ef068e790c | |||
6f39f115b7 | |||
23c7302c04 | |||
e219f3adf1 | |||
2c7210d85e | |||
ade37e92f3 | |||
ef3bc83069 | |||
14084f13d8 | |||
fbc953cf46 | |||
fd7974fc98 | |||
d17769d69f | |||
7ba04f26a3 | |||
11235b6837 | |||
e8849048db | |||
cd15337ad8 | |||
037261991a | |||
67fb5891e4 | |||
d1f6d1e742 | |||
18fc2c3a7e | |||
54486b119b | |||
04a633d501 | |||
612203f014 | |||
e9ba336f68 | |||
94790ce7dc | |||
023063327f | |||
d46aa878af | |||
2462a99fdc | |||
fc18bc6a18 | |||
b0f0934fa4 | |||
f6721d5e2c | |||
0d43a8be54 | |||
0193921053 | |||
485f6e8319 | |||
09ee251c9e | |||
75011ef8cb | |||
69c7af5220 | |||
a364abe887 | |||
95accc6d3f | |||
04fb6cce8e | |||
5420214bdd | |||
b4f26787d4 | |||
51dff0361d | |||
3187830ff5 | |||
338322619c | |||
511f83bfd9 | |||
b0f5c38fb0 | |||
520018a86d | |||
39c2e58fbc | |||
d1d6eb5c8b | |||
84920a88ab | |||
bece7f33c7 |
34
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
34
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
name: Bug Report
|
||||
description: File a bug report.
|
||||
title: "bug: "
|
||||
labels: ["bug"]
|
||||
assignees:
|
||||
- edera-dev/engineering
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: Also tell us, what did you expect to happen?
|
||||
placeholder: Tell us what you see!
|
||||
value: "A bug happened!"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version / Commit
|
||||
description: What version of our software are you running?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
|
25
.github/dependabot.yml
vendored
25
.github/dependabot.yml
vendored
@ -4,7 +4,32 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
groups:
|
||||
dep-updates:
|
||||
dependency-type: "production"
|
||||
applies-to: "version-updates"
|
||||
dev-updates:
|
||||
dependency-type: "development"
|
||||
applies-to: "version-updates"
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
groups:
|
||||
dep-updates:
|
||||
dependency-type: "production"
|
||||
applies-to: "version-updates"
|
||||
dev-updates:
|
||||
dependency-type: "development"
|
||||
applies-to: "version-updates"
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/images"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
groups:
|
||||
dep-updates:
|
||||
dependency-type: "production"
|
||||
applies-to: "version-updates"
|
||||
dev-updates:
|
||||
dependency-type: "development"
|
||||
applies-to: "version-updates"
|
||||
|
193
.github/workflows/check.yml
vendored
193
.github/workflows/check.yml
vendored
@ -7,23 +7,196 @@ on:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
fmt:
|
||||
name: fmt
|
||||
rustfmt:
|
||||
name: rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: harden runner
|
||||
uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
- run: ./hack/ci/install-linux-deps.sh
|
||||
- run: ./hack/build/cargo.sh fmt --all -- --check
|
||||
- name: install stable rust toolchain with rustfmt
|
||||
run: |
|
||||
rustup update --no-self-update stable
|
||||
rustup default stable
|
||||
rustup component add rustfmt
|
||||
- name: install linux dependencies
|
||||
run: ./hack/ci/install-linux-deps.sh
|
||||
# Temporarily ignored: https://github.com/edera-dev/krata/issues/206
|
||||
- name: cargo fmt
|
||||
run: ./hack/build/cargo.sh fmt --all -- --check || true
|
||||
shellcheck:
|
||||
name: shellcheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: harden runner
|
||||
uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
with:
|
||||
submodules: recursive
|
||||
- run: ./hack/code/shellcheck.sh
|
||||
- name: shellcheck
|
||||
run: ./hack/code/shellcheck.sh
|
||||
full-build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch:
|
||||
- x86_64
|
||||
- aarch64
|
||||
env:
|
||||
TARGET_ARCH: "${{ matrix.arch }}"
|
||||
name: full build linux-${{ matrix.arch }}
|
||||
steps:
|
||||
- name: harden runner
|
||||
uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
|
||||
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 linux dependencies
|
||||
run: ./hack/ci/install-linux-deps.sh
|
||||
- name: cargo build
|
||||
run: ./hack/build/cargo.sh build
|
||||
full-test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch:
|
||||
- x86_64
|
||||
- aarch64
|
||||
env:
|
||||
TARGET_ARCH: "${{ matrix.arch }}"
|
||||
name: full test linux-${{ matrix.arch }}
|
||||
steps:
|
||||
- name: harden runner
|
||||
uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
|
||||
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 linux dependencies
|
||||
run: ./hack/ci/install-linux-deps.sh
|
||||
- name: cargo test
|
||||
run: ./hack/build/cargo.sh test
|
||||
full-clippy:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
arch:
|
||||
- x86_64
|
||||
- aarch64
|
||||
env:
|
||||
TARGET_ARCH: "${{ matrix.arch }}"
|
||||
name: full clippy linux-${{ matrix.arch }}
|
||||
steps:
|
||||
- name: harden runner
|
||||
uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: install stable rust toolchain with clippy
|
||||
run: |
|
||||
rustup update --no-self-update stable
|
||||
rustup default stable
|
||||
rustup component add clippy
|
||||
- name: install linux dependencies
|
||||
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@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
|
||||
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@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
|
||||
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
|
||||
|
44
.github/workflows/client.yml
vendored
44
.github/workflows/client.yml
vendored
@ -1,44 +0,0 @@
|
||||
name: client
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
merge_group:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
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: client build ${{ matrix.platform.os }}-${{ matrix.platform.arch }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- run: git config --global core.autocrlf false && git config --global core.eol lf
|
||||
if: ${{ matrix.platform.os == 'windows' }}
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
if: ${{ matrix.platform.os != 'darwin' }}
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: "${{ matrix.platform.arch }}-apple-darwin"
|
||||
if: ${{ matrix.platform.os == 'darwin' }}
|
||||
- uses: homebrew/actions/setup-homebrew@master
|
||||
if: ${{ matrix.platform.os == 'darwin' }}
|
||||
- run: ./hack/ci/install-${{ matrix.platform.deps }}-deps.sh
|
||||
- run: ./hack/build/cargo.sh build --bin kratactl
|
32
.github/workflows/kernel.yml
vendored
32
.github/workflows/kernel.yml
vendored
@ -1,32 +0,0 @@
|
||||
name: kernel
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "kernel/**"
|
||||
- "hack/ci/**"
|
||||
merge_group:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch:
|
||||
- x86_64
|
||||
- aarch64
|
||||
env:
|
||||
TARGET_ARCH: "${{ matrix.arch }}"
|
||||
name: kernel build ${{ matrix.arch }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: ./hack/ci/install-linux-deps.sh
|
||||
- run: ./hack/kernel/build.sh
|
||||
env:
|
||||
KRATA_KERNEL_BUILD_JOBS: "5"
|
145
.github/workflows/nightly.yml
vendored
145
.github/workflows/nightly.yml
vendored
@ -3,8 +3,10 @@ on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 10 * * *"
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
server:
|
||||
full-build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@ -14,48 +16,49 @@ jobs:
|
||||
- aarch64
|
||||
env:
|
||||
TARGET_ARCH: "${{ matrix.arch }}"
|
||||
name: nightly server ${{ matrix.arch }}
|
||||
CI_NEEDS_FPM: "1"
|
||||
name: nightly full build linux-${{ matrix.arch }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: harden runner
|
||||
uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: "${{ matrix.arch }}-unknown-linux-gnu,${{ matrix.arch }}-unknown-linux-musl"
|
||||
- run: ./hack/ci/install-linux-deps.sh
|
||||
- run: ./hack/dist/bundle.sh
|
||||
env:
|
||||
KRATA_KERNEL_BUILD_JOBS: "5"
|
||||
- uses: actions/upload-artifact@v4
|
||||
- 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@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5
|
||||
with:
|
||||
name: krata-bundle-systemd-${{ matrix.arch }}
|
||||
path: "target/dist/bundle-systemd-${{ matrix.arch }}.tgz"
|
||||
compression-level: 0
|
||||
- run: ./hack/dist/deb.sh
|
||||
env:
|
||||
KRATA_KERNEL_BUILD_SKIP: "1"
|
||||
- uses: actions/upload-artifact@v4
|
||||
- name: build deb package
|
||||
run: ./hack/dist/deb.sh
|
||||
- name: upload deb package
|
||||
uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5
|
||||
with:
|
||||
name: krata-debian-${{ matrix.arch }}
|
||||
path: "target/dist/*.deb"
|
||||
compression-level: 0
|
||||
- run: ./hack/dist/apk.sh
|
||||
env:
|
||||
KRATA_KERNEL_BUILD_SKIP: "1"
|
||||
- uses: actions/upload-artifact@v4
|
||||
- name: build apk package
|
||||
run: ./hack/dist/apk.sh
|
||||
- name: upload apk package
|
||||
uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5
|
||||
with:
|
||||
name: krata-alpine-${{ matrix.arch }}
|
||||
path: "target/dist/*_${{ matrix.arch }}.apk"
|
||||
compression-level: 0
|
||||
- run: ./hack/os/build.sh
|
||||
env:
|
||||
KRATA_KERNEL_BUILD_SKIP: "1"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: krata-os-${{ matrix.arch }}
|
||||
path: "target/os/krata-${{ matrix.arch }}.qcow2"
|
||||
compression-level: 0
|
||||
client:
|
||||
kratactl-build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@ -70,33 +73,93 @@ jobs:
|
||||
TARGET_OS: "${{ matrix.platform.os }}"
|
||||
TARGET_ARCH: "${{ matrix.platform.arch }}"
|
||||
runs-on: "${{ matrix.platform.on }}"
|
||||
name: nightly client ${{ matrix.platform.os }}-${{ matrix.platform.arch }}
|
||||
name: nightly kratactl build ${{ matrix.platform.os }}-${{ matrix.platform.arch }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- run: git config --global core.autocrlf false && git config --global core.eol lf
|
||||
- name: harden runner
|
||||
uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
|
||||
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' }}
|
||||
- uses: actions/checkout@v4
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
if: ${{ matrix.platform.os != 'darwin' }}
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: "${{ matrix.platform.arch }}-apple-darwin"
|
||||
- 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' }}
|
||||
- uses: homebrew/actions/setup-homebrew@master
|
||||
- name: setup homebrew
|
||||
uses: homebrew/actions/setup-homebrew@4b34604e75af8f8b23b454f0b5ffb7c5d8ce0056 # master
|
||||
if: ${{ matrix.platform.os == 'darwin' }}
|
||||
- run: ./hack/ci/install-${{ matrix.platform.deps }}-deps.sh
|
||||
- run: ./hack/build/cargo.sh build --release --bin kratactl
|
||||
- uses: actions/upload-artifact@v4
|
||||
- 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@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5
|
||||
with:
|
||||
name: kratactl-${{ matrix.platform.os }}-${{ matrix.platform.arch }}
|
||||
path: "target/*/release/kratactl"
|
||||
if: ${{ matrix.platform.os != 'windows' }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
- name: upload kratactl
|
||||
uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5
|
||||
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@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: install cosign
|
||||
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.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@5176d81f87c23d6fc96624dfdbcd9f3830bbe445 # v6.5.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"
|
||||
|
40
.github/workflows/os.yml
vendored
40
.github/workflows/os.yml
vendored
@ -1,40 +0,0 @@
|
||||
name: os
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "os/**"
|
||||
- "hack/os/**"
|
||||
- "hack/ci/**"
|
||||
merge_group:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch:
|
||||
- x86_64
|
||||
- aarch64
|
||||
env:
|
||||
TARGET_ARCH: "${{ matrix.arch }}"
|
||||
name: os build ${{ matrix.arch }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: "${{ matrix.arch }}-unknown-linux-gnu,${{ matrix.arch }}-unknown-linux-musl"
|
||||
- run: ./hack/ci/install-linux-deps.sh
|
||||
- run: ./hack/os/build.sh
|
||||
env:
|
||||
KRATA_KERNEL_BUILD_JOBS: "5"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: krata-os-${{ matrix.arch }}
|
||||
path: "target/os/krata-${{ matrix.arch }}.qcow2"
|
||||
compression-level: 0
|
172
.github/workflows/release-assets.yml
vendored
Normal file
172
.github/workflows/release-assets.yml
vendored
Normal file
@ -0,0 +1,172 @@
|
||||
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@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
|
||||
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@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
|
||||
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@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: install cosign
|
||||
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.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@5176d81f87c23d6fc96624dfdbcd9f3830bbe445 # v6.5.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"
|
94
.github/workflows/release-binaries.yml
vendored
94
.github/workflows/release-binaries.yml
vendored
@ -1,94 +0,0 @@
|
||||
name: release-binaries
|
||||
permissions:
|
||||
contents: write
|
||||
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:
|
||||
server:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch:
|
||||
- x86_64
|
||||
- aarch64
|
||||
env:
|
||||
TARGET_ARCH: "${{ matrix.arch }}"
|
||||
name: release-binaries server ${{ matrix.arch }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: "${{ matrix.arch }}-unknown-linux-gnu,${{ matrix.arch }}-unknown-linux-musl"
|
||||
- run: ./hack/ci/install-linux-deps.sh
|
||||
- run: ./hack/dist/bundle.sh
|
||||
env:
|
||||
KRATA_KERNEL_BUILD_JOBS: "5"
|
||||
- run: "./hack/ci/assemble-release-assets.sh bundle-systemd ${{ github.event.release.tag_name }} ${{ matrix.arch }} target/dist/bundle-systemd-${{ matrix.arch }}.tgz"
|
||||
- run: ./hack/dist/deb.sh
|
||||
env:
|
||||
KRATA_KERNEL_BUILD_SKIP: "1"
|
||||
- run: "./hack/ci/assemble-release-assets.sh debian ${{ github.event.release.tag_name }} ${{ matrix.arch }} target/dist/*.deb"
|
||||
- run: ./hack/dist/apk.sh
|
||||
env:
|
||||
KRATA_KERNEL_BUILD_SKIP: "1"
|
||||
- run: "./hack/ci/assemble-release-assets.sh alpine ${{ github.event.release.tag_name }} ${{ matrix.arch }} target/dist/*_${{ matrix.arch }}.apk"
|
||||
- run: ./hack/os/build.sh
|
||||
env:
|
||||
KRATA_KERNEL_BUILD_SKIP: "1"
|
||||
- run: "./hack/ci/assemble-release-assets.sh os ${{ github.event.release.tag_name }} ${{ matrix.arch }} target/os/krata-${{ matrix.arch }}.qcow2"
|
||||
- run: "./hack/ci/upload-release-assets.sh ${{ github.event.release.tag_name }}"
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
client:
|
||||
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-binaries client ${{ matrix.platform.os }}-${{ matrix.platform.arch }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
if: ${{ matrix.platform.os != 'darwin' }}
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: "${{ matrix.platform.arch }}-apple-darwin"
|
||||
if: ${{ matrix.platform.os == 'darwin' }}
|
||||
- uses: homebrew/actions/setup-homebrew@master
|
||||
if: ${{ matrix.platform.os == 'darwin' }}
|
||||
- run: ./hack/ci/install-${{ matrix.platform.deps }}-deps.sh
|
||||
- run: ./hack/build/cargo.sh build --release --bin kratactl
|
||||
- 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' }}
|
||||
- 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' }}
|
||||
- run: "./hack/ci/upload-release-assets.sh ${{ github.event.release.tag_name }}"
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
50
.github/workflows/release-plz.yml
vendored
50
.github/workflows/release-plz.yml
vendored
@ -1,7 +1,4 @@
|
||||
name: release-plz
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@ -13,21 +10,34 @@ jobs:
|
||||
release-plz:
|
||||
name: release-plz
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
id: generate-token
|
||||
with:
|
||||
app-id: "${{ secrets.EDERA_CULTIVATION_APP_ID }}"
|
||||
private-key: "${{ secrets.EDERA_CULTIVATION_APP_PRIVATE_KEY }}"
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
token: "${{ steps.generate-token.outputs.token }}"
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: ./hack/ci/install-linux-deps.sh
|
||||
- name: release-plz
|
||||
uses: MarcoIeni/release-plz-action@v0.5
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ steps.generate-token.outputs.token }}"
|
||||
CARGO_REGISTRY_TOKEN: "${{ secrets.KRATA_RELEASE_CARGO_TOKEN }}"
|
||||
- name: harden runner
|
||||
uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
- name: generate cultivator token
|
||||
uses: actions/create-github-app-token@31c86eb3b33c9b601a1f60f98dcbfd1d70f379b4 # v1.10.3
|
||||
id: generate-token
|
||||
with:
|
||||
app-id: "${{ secrets.EDERA_CULTIVATION_APP_ID }}"
|
||||
private-key: "${{ secrets.EDERA_CULTIVATION_APP_PRIVATE_KEY }}"
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
token: "${{ steps.generate-token.outputs.token }}"
|
||||
- name: install stable rust toolchain
|
||||
run: |
|
||||
rustup update --no-self-update stable
|
||||
rustup default stable
|
||||
- name: install linux dependencies
|
||||
run: ./hack/ci/install-linux-deps.sh
|
||||
- name: release-plz
|
||||
uses: MarcoIeni/release-plz-action@92ae919a6b3e27c0472659e3a7414ff4a00e833f # v0.5.64
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ steps.generate-token.outputs.token }}"
|
||||
CARGO_REGISTRY_TOKEN: "${{ secrets.KRATA_RELEASE_CARGO_TOKEN }}"
|
||||
|
82
.github/workflows/server.yml
vendored
82
.github/workflows/server.yml
vendored
@ -1,82 +0,0 @@
|
||||
name: server
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
merge_group:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch:
|
||||
- x86_64
|
||||
- aarch64
|
||||
env:
|
||||
TARGET_ARCH: "${{ matrix.arch }}"
|
||||
name: server build ${{ matrix.arch }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: ./hack/ci/install-linux-deps.sh
|
||||
- run: ./hack/build/cargo.sh build
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
arch:
|
||||
- x86_64
|
||||
- aarch64
|
||||
env:
|
||||
TARGET_ARCH: "${{ matrix.arch }}"
|
||||
name: server test ${{ matrix.arch }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: ./hack/ci/install-linux-deps.sh
|
||||
- run: ./hack/build/cargo.sh test
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
arch:
|
||||
- x86_64
|
||||
- aarch64
|
||||
env:
|
||||
TARGET_ARCH: "${{ matrix.arch }}"
|
||||
name: server clippy ${{ matrix.arch }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
- run: ./hack/ci/install-linux-deps.sh
|
||||
- run: ./hack/build/cargo.sh clippy
|
||||
initrd:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
arch:
|
||||
- x86_64
|
||||
- aarch64
|
||||
env:
|
||||
TARGET_ARCH: "${{ matrix.arch }}"
|
||||
name: server initrd ${{ matrix.arch }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: "${{ matrix.arch }}-unknown-linux-gnu,${{ matrix.arch }}-unknown-linux-musl"
|
||||
- run: ./hack/ci/install-linux-deps.sh
|
||||
- run: ./hack/initrd/build.sh
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "vendor"]
|
||||
path = vendor
|
||||
url = https://github.com/edera-dev/krata-vendor.git
|
62
CHANGELOG.md
62
CHANGELOG.md
@ -6,6 +6,68 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [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
|
||||
|
34
CONTRIBUTING.md
Normal file
34
CONTRIBUTING.md
Normal file
@ -0,0 +1,34 @@
|
||||
# Contributing to Krata
|
||||
|
||||
Welcome! We're very glad you're reading this; Edera is excited for all kinds of contributions! Please read the following to ensure you're aware of our flow and policies.
|
||||
|
||||
## Before contributing
|
||||
|
||||
1. Please read our [Code of Conduct](CODE_OF_CONDUCT.md), which applies to all interactions in/with all Edera projects and venues.
|
||||
2. Before opening an issue or PR, please try a few searches to see if there is overlap with existing conversations or WIP contributions.
|
||||
3. For security or otherwise sensitive topics, please read our [Security Policy].
|
||||
4. Ask questions! If you want to ask something, chances are someone else wants to ask it as well.
|
||||
|
||||
## Contributing Code
|
||||
|
||||
To get started with technical contributions, please read out [Development Guide]. If you're looking for something easy to tackle, [look for issues labeled `good first issue`][good-first-issue].
|
||||
|
||||
## Reporting bugs and other issues
|
||||
|
||||
While it's totally fine to simply bring it up on our Discord, we encourage opening an issue on GitHub using the Bug Report template.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
1. For anything more than simple bug/doc fixes, please open a GitHub issue for tracking purposes.
|
||||
- Else skip to step 3.
|
||||
2. Discuss the change with the teams to ensure we have consensus on the change being welcome.
|
||||
3. We encourage opening the PR sooner than later, and prefixing with `WIP:` so GitHub labels it as a Draft.
|
||||
4. Please include a detailed list of changes that the PR makes.
|
||||
5. Once the PR is ready for review, remove the Draft status, and request a review from `edera-dev/engineering`.
|
||||
6. After the review cycle concludes and we know you are ready for merging, a team member will submit the PR to the merge queue.
|
||||
|
||||
|
||||
[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
|
927
Cargo.lock
generated
927
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
75
Cargo.toml
75
Cargo.toml
@ -1,8 +1,9 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/build",
|
||||
"crates/krata",
|
||||
"crates/oci",
|
||||
"crates/guest",
|
||||
"crates/zone",
|
||||
"crates/runtime",
|
||||
"crates/daemon",
|
||||
"crates/network",
|
||||
@ -11,12 +12,13 @@ members = [
|
||||
"crates/xen/xenclient",
|
||||
"crates/xen/xenevtchn",
|
||||
"crates/xen/xengnt",
|
||||
"crates/xen/xenplatform",
|
||||
"crates/xen/xenstore",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.0.10"
|
||||
version = "0.0.15"
|
||||
homepage = "https://krata.dev"
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/edera-dev/krata"
|
||||
@ -24,80 +26,87 @@ repository = "https://github.com/edera-dev/krata"
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0"
|
||||
arrayvec = "0.7.4"
|
||||
async-compression = "0.4.8"
|
||||
async-compression = "0.4.12"
|
||||
async-stream = "0.3.5"
|
||||
async-trait = "0.1.80"
|
||||
backhand = "0.15.0"
|
||||
base64 = "0.22.0"
|
||||
async-trait = "0.1.81"
|
||||
backhand = "0.18.0"
|
||||
base64 = "0.22.1"
|
||||
byteorder = "1"
|
||||
bytes = "1.5.0"
|
||||
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.27.0"
|
||||
ctrlc = "3.4.4"
|
||||
elf = "0.7.4"
|
||||
env_logger = "0.11.0"
|
||||
etherparse = "0.14.3"
|
||||
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.6"
|
||||
human_bytes = "0.4"
|
||||
indexmap = "2.2.6"
|
||||
indexmap = "2.3.0"
|
||||
indicatif = "0.17.8"
|
||||
ipnetwork = "0.20.0"
|
||||
libc = "0.2"
|
||||
log = "0.4.20"
|
||||
log = "0.4.22"
|
||||
loopdev-3 = "0.5.1"
|
||||
krata-advmac = "1.1.0"
|
||||
krata-tokio-tar = "0.4.0"
|
||||
memchr = "2"
|
||||
nix = "0.28.0"
|
||||
oci-spec = "0.6.4"
|
||||
nix = "0.29.0"
|
||||
oci-spec = "0.6.8"
|
||||
once_cell = "1.19.0"
|
||||
path-absolutize = "3.1.1"
|
||||
path-clean = "1.0.1"
|
||||
prost = "0.12.4"
|
||||
prost-build = "0.12.4"
|
||||
prost-reflect-build = "0.13.0"
|
||||
prost-types = "0.12.4"
|
||||
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"
|
||||
rand = "0.8.5"
|
||||
ratatui = "0.26.2"
|
||||
redb = "2.1.0"
|
||||
ratatui = "0.27.0"
|
||||
redb = "2.1.1"
|
||||
regex = "1.10.6"
|
||||
rtnetlink = "0.14.1"
|
||||
scopeguard = "1.2.0"
|
||||
serde_json = "1.0.116"
|
||||
serde_json = "1.0.122"
|
||||
serde_yaml = "0.9"
|
||||
sha256 = "1.5.0"
|
||||
signal-hook = "0.3.17"
|
||||
slice-copy = "0.3.0"
|
||||
smoltcp = "0.11.0"
|
||||
sysinfo = "0.30.11"
|
||||
termtree = "0.4.1"
|
||||
sysinfo = "0.30.13"
|
||||
termtree = "0.5.1"
|
||||
thiserror = "1.0"
|
||||
tokio-tun = "0.11.4"
|
||||
tonic-build = "0.11.0"
|
||||
tokio-tun = "0.11.5"
|
||||
toml = "0.8.19"
|
||||
tonic-build = "0.12.1"
|
||||
tower = "0.4.13"
|
||||
udp-stream = "0.0.11"
|
||||
url = "2.5.0"
|
||||
udp-stream = "0.0.12"
|
||||
url = "2.5.2"
|
||||
walkdir = "2"
|
||||
xz2 = "0.1"
|
||||
|
||||
[workspace.dependencies.clap]
|
||||
version = "4.4.18"
|
||||
version = "4.5.13"
|
||||
features = ["derive"]
|
||||
|
||||
[workspace.dependencies.prost-reflect]
|
||||
version = "0.13.1"
|
||||
version = "0.14.0"
|
||||
features = ["derive"]
|
||||
|
||||
[workspace.dependencies.reqwest]
|
||||
version = "0.12.4"
|
||||
version = "0.12.5"
|
||||
default-features = false
|
||||
features = ["rustls-tls"]
|
||||
|
||||
[workspace.dependencies.serde]
|
||||
version = "1.0.198"
|
||||
version = "1.0.204"
|
||||
features = ["derive"]
|
||||
|
||||
[workspace.dependencies.sys-mount]
|
||||
@ -105,7 +114,7 @@ version = "3.0.0"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.tokio]
|
||||
version = "1.35.1"
|
||||
version = "1.39.2"
|
||||
features = ["full"]
|
||||
|
||||
[workspace.dependencies.tokio-stream]
|
||||
@ -113,11 +122,11 @@ version = "0.1"
|
||||
features = ["io-util", "net"]
|
||||
|
||||
[workspace.dependencies.tonic]
|
||||
version = "0.11.0"
|
||||
version = "0.12.1"
|
||||
features = ["tls"]
|
||||
|
||||
[workspace.dependencies.uuid]
|
||||
version = "1.6.1"
|
||||
version = "1.10.0"
|
||||
features = ["v4"]
|
||||
|
||||
[profile.release]
|
||||
|
60
DEV.md
60
DEV.md
@ -4,25 +4,25 @@
|
||||
|
||||
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 |
|
||||
| krataguest | guest | none, guest init | N/A | crates/guest |
|
||||
| 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 will lower memory limit to give krata guests room |
|
||||
| Xen | 4.17 | Temporary due to hardcoded interface version constants |
|
||||
| Debian | stable / sid | Debian is recommended due to the ease of Xen setup |
|
||||
| rustup | any | Install Rustup from https://rustup.rs |
|
||||
| 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
|
||||
|
||||
@ -31,7 +31,8 @@ it's corresponding code path from the above table.
|
||||
2. Install required packages:
|
||||
|
||||
```sh
|
||||
$ apt install git xen-system-amd64 build-essential libclang-dev musl-tools flex bison libelf-dev libssl-dev bc protobuf-compiler libprotobuf-dev squashfs-tools erofs-utils
|
||||
$ 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.
|
||||
@ -43,10 +44,10 @@ $ 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 guests some room:
|
||||
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 guests.
|
||||
# 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"
|
||||
```
|
||||
|
||||
@ -62,29 +63,36 @@ $ git clone https://github.com/edera-dev/krata.git krata
|
||||
$ cd krata
|
||||
```
|
||||
|
||||
6. Build a guest kernel image:
|
||||
6. Fetch the zone kernel image:
|
||||
|
||||
```sh
|
||||
$ ./hack/kernel/build.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
|
||||
```
|
||||
|
||||
7. Copy the guest kernel image at `target/kernel/kernel-x86_64` to `/var/lib/krata/guest/kernel` to have it automatically detected by kratad.
|
||||
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 guest:
|
||||
10. Run `kratactl` to launch a zone:
|
||||
|
||||
```sh
|
||||
$ ./hack/debug/kratactl.sh launch --attach alpine:latest
|
||||
$ ./hack/debug/kratactl.sh zone launch --attach alpine:latest
|
||||
```
|
||||
|
||||
To detach from the guest console, use `Ctrl + ]` on your keyboard.
|
||||
To detach from the zone console, use `Ctrl + ]` on your keyboard.
|
||||
|
||||
To list the running guests, run:
|
||||
To list the running zones, run:
|
||||
```sh
|
||||
$ ./hack/debug/kratactl.sh list
|
||||
$ ./hack/debug/kratactl.sh zone list
|
||||
```
|
||||
|
||||
To destroy a running guest, copy it's UUID from either the launch command or the guest list and run:
|
||||
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 destroy GUEST_UUID
|
||||
$ ./hack/debug/kratactl.sh zone destroy ZONE_UUID
|
||||
```
|
||||
|
12
FAQ.md
12
FAQ.md
@ -2,18 +2,14 @@
|
||||
|
||||
## How does krata currently work?
|
||||
|
||||
The krata hypervisor 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 which the init container can read.
|
||||
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 layer 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.
|
||||
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 otherhand provides a minimal amount of attack surface upon which less-trusted guests can be launched on top of.
|
||||
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.
|
||||
|
||||
## Why not utilize pvcalls to provide access to the host network?
|
||||
|
||||
pvcalls is extremely interesting, and although it is certainly possible to utilize pvcalls to get the job done, we chose to utilize userspace networking technology in order to enhance security. Our goal is to drop the use of all xen networking backend drivers within the kernel and have the guest talk directly to a userspace daemon, bypassing the vif (xen-netback) driver. Currently, in order to develop the networking layer, we utilize xen-netback and then use raw sockets to provide the userspace networking layer on the host.
|
||||
|
||||
## What are the future plans?
|
||||
|
||||
Edera is building a company to compete in the hypervisor space with open-source technology. More information to come soon on official channels.
|
||||
pvcalls is fascinating, and although it is certainly possible to utilize pvcalls to get the job done, we chose to utilize userspace networking technology in order to enhance security. Our goal is to drop the use of all xen networking backend drivers within the kernel and have the guest talk directly to a userspace daemon, bypassing the vif (xen-netback) driver. Currently, in order to develop the networking layer, we utilize xen-netback and then use raw sockets to provide the userspace networking layer on the host.
|
||||
|
18
README.md
18
README.md
@ -1,6 +1,10 @@
|
||||
# krata
|
||||
|
||||
The Edera Hypervisor
|
||||
An isolation engine for securing compute workloads.
|
||||
|
||||
```bash
|
||||
$ kratactl zone launch -a alpine:latest
|
||||
```
|
||||
|
||||

|
||||

|
||||
@ -16,13 +20,13 @@ The Edera Hypervisor
|
||||
|
||||
## Introduction
|
||||
|
||||
krata is a single-host hypervisor service built for OCI-compliant containers. It isolates containers using a type-1 hypervisor, providing workload isolation that can exceed the security level of KVM-based OCI-compliant runtimes.
|
||||
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 to bring Xen tooling into a new secure era.
|
||||
krata utilizes the core of the Xen hypervisor with a fully memory-safe Rust control plane.
|
||||
|
||||
## Hardware Support
|
||||
|
||||
| Architecture | Completion Level | Virtualization Technology |
|
||||
| ------------ | ---------------- | ------------------------- |
|
||||
| x86_64 | 100% Completed | Intel VT-x, AMD-V |
|
||||
| aarch64 | 30% Completed | AArch64 virtualization |
|
||||
| Architecture | Completion Level | Hardware Virtualization |
|
||||
|--------------|------------------|-------------------------|
|
||||
| x86_64 | 100% Completed | None, Intel VT-x, AMD-V |
|
||||
| aarch64 | 10% Completed | AArch64 virtualization |
|
||||
|
25
crates/build/Cargo.toml
Normal file
25
crates/build/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[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.15" }
|
||||
krata-tokio-tar = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
|
||||
[[bin]]
|
||||
name = "build-fetch-kernel"
|
||||
path = "bin/fetch_kernel.rs"
|
121
crates/build/bin/fetch_kernel.rs
Normal file
121
crates/build/bin/fetch_kernel.rs
Normal file
@ -0,0 +1,121 @@
|
||||
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(())
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "krata-ctl"
|
||||
description = "Command-line tool to control the krata hypervisor"
|
||||
description = "Command-line tool to control the krata isolation engine"
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
homepage.workspace = true
|
||||
@ -20,7 +20,7 @@ env_logger = { workspace = true }
|
||||
fancy-duration = { workspace = true }
|
||||
human_bytes = { workspace = true }
|
||||
indicatif = { workspace = true }
|
||||
krata = { path = "../krata", version = "^0.0.10" }
|
||||
krata = { path = "../krata", version = "^0.0.15" }
|
||||
log = { workspace = true }
|
||||
prost-reflect = { workspace = true, features = ["serde"] }
|
||||
prost-types = { workspace = true }
|
||||
|
128
crates/ctl/src/cli/device/list.rs
Normal file
128
crates/ctl/src/cli/device/list.rs
Normal file
@ -0,0 +1,128 @@
|
||||
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 the devices on the isolation engine")]
|
||||
pub struct DeviceListCommand {
|
||||
#[arg(short, long, default_value = "table", help = "Output format")]
|
||||
format: DeviceListFormat,
|
||||
}
|
||||
|
||||
impl DeviceListCommand {
|
||||
pub async fn run(
|
||||
self,
|
||||
mut client: ControlServiceClient<Channel>,
|
||||
_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<DeviceInfo>) -> 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<DeviceInfo>) -> Result<()> {
|
||||
for device in devices {
|
||||
let kvs = proto2kv(device)?;
|
||||
println!("{}", kv2line(kvs));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
44
crates/ctl/src/cli/device/mod.rs
Normal file
44
crates/ctl/src/cli/device/mod.rs
Normal file
@ -0,0 +1,44 @@
|
||||
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<Channel>,
|
||||
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<Channel>,
|
||||
events: EventStream,
|
||||
) -> Result<()> {
|
||||
match self {
|
||||
DeviceCommands::List(list) => list.run(client, events).await,
|
||||
}
|
||||
}
|
||||
}
|
60
crates/ctl/src/cli/host/cpu_topology.rs
Normal file
60
crates/ctl/src/cli/host/cpu_topology.rs
Normal file
@ -0,0 +1,60 @@
|
||||
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, HostCpuTopologyClass, HostCpuTopologyRequest,
|
||||
};
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
#[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<Channel>) -> Result<()> {
|
||||
let response = client
|
||||
.get_host_cpu_topology(Request::new(HostCpuTopologyRequest {}))
|
||||
.await?
|
||||
.into_inner();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -6,9 +6,9 @@ use tonic::{transport::Channel, Request};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Identify information about the host")]
|
||||
pub struct IdentifyHostCommand {}
|
||||
pub struct HostIdentifyCommand {}
|
||||
|
||||
impl IdentifyHostCommand {
|
||||
impl HostIdentifyCommand {
|
||||
pub async fn run(self, mut client: ControlServiceClient<Channel>) -> Result<()> {
|
||||
let response = client
|
||||
.identify_host(Request::new(IdentifyHostRequest {}))
|
@ -15,7 +15,7 @@ use tonic::transport::Channel;
|
||||
use crate::format::{kv2line, proto2dynamic, value2kv};
|
||||
|
||||
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
|
||||
enum IdmSnoopFormat {
|
||||
enum HostIdmSnoopFormat {
|
||||
Simple,
|
||||
Jsonl,
|
||||
KeyValue,
|
||||
@ -23,12 +23,12 @@ enum IdmSnoopFormat {
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Snoop on the IDM bus")]
|
||||
pub struct IdmSnoopCommand {
|
||||
pub struct HostIdmSnoopCommand {
|
||||
#[arg(short, long, default_value = "simple", help = "Output format")]
|
||||
format: IdmSnoopFormat,
|
||||
format: HostIdmSnoopFormat,
|
||||
}
|
||||
|
||||
impl IdmSnoopCommand {
|
||||
impl HostIdmSnoopCommand {
|
||||
pub async fn run(
|
||||
self,
|
||||
mut client: ControlServiceClient<Channel>,
|
||||
@ -43,16 +43,16 @@ impl IdmSnoopCommand {
|
||||
};
|
||||
|
||||
match self.format {
|
||||
IdmSnoopFormat::Simple => {
|
||||
HostIdmSnoopFormat::Simple => {
|
||||
self.print_simple(line)?;
|
||||
}
|
||||
|
||||
IdmSnoopFormat::Jsonl => {
|
||||
HostIdmSnoopFormat::Jsonl => {
|
||||
let encoded = serde_json::to_string(&line)?;
|
||||
println!("{}", encoded.trim());
|
||||
}
|
||||
|
||||
IdmSnoopFormat::KeyValue => {
|
||||
HostIdmSnoopFormat::KeyValue => {
|
||||
self.print_key_value(line)?;
|
||||
}
|
||||
}
|
54
crates/ctl/src/cli/host/mod.rs
Normal file
54
crates/ctl/src/cli/host/mod.rs
Normal file
@ -0,0 +1,54 @@
|
||||
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::identify::HostIdentifyCommand;
|
||||
use crate::cli::host::idm_snoop::HostIdmSnoopCommand;
|
||||
|
||||
pub mod cpu_topology;
|
||||
pub mod identify;
|
||||
pub mod idm_snoop;
|
||||
|
||||
#[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<Channel>,
|
||||
events: EventStream,
|
||||
) -> Result<()> {
|
||||
self.subcommand.run(client, events).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum HostCommands {
|
||||
CpuTopology(HostCpuTopologyCommand),
|
||||
Identify(HostIdentifyCommand),
|
||||
IdmSnoop(HostIdmSnoopCommand),
|
||||
}
|
||||
|
||||
impl HostCommands {
|
||||
pub async fn run(
|
||||
self,
|
||||
client: ControlServiceClient<Channel>,
|
||||
events: EventStream,
|
||||
) -> Result<()> {
|
||||
match self {
|
||||
HostCommands::CpuTopology(cpu_topology) => cpu_topology.run(client).await,
|
||||
|
||||
HostCommands::Identify(identify) => identify.run(client).await,
|
||||
|
||||
HostCommands::IdmSnoop(snoop) => snoop.run(client, events).await,
|
||||
}
|
||||
}
|
||||
}
|
44
crates/ctl/src/cli/image/mod.rs
Normal file
44
crates/ctl/src/cli/image/mod.rs
Normal file
@ -0,0 +1,44 @@
|
||||
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<Channel>,
|
||||
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<Channel>,
|
||||
_events: EventStream,
|
||||
) -> Result<()> {
|
||||
match self {
|
||||
ImageCommands::Pull(pull) => pull.run(client).await,
|
||||
}
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ use tonic::transport::Channel;
|
||||
use crate::pull::pull_interactive_progress;
|
||||
|
||||
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum PullImageFormat {
|
||||
pub enum ImagePullImageFormat {
|
||||
Squashfs,
|
||||
Erofs,
|
||||
Tar,
|
||||
@ -18,26 +18,27 @@ pub enum PullImageFormat {
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Pull an image into the cache")]
|
||||
pub struct PullCommand {
|
||||
pub struct ImagePullCommand {
|
||||
#[arg(help = "Image name")]
|
||||
image: String,
|
||||
#[arg(short = 's', long, default_value = "squashfs", help = "Image format")]
|
||||
image_format: PullImageFormat,
|
||||
image_format: ImagePullImageFormat,
|
||||
#[arg(short = 'o', long, help = "Overwrite image cache")]
|
||||
overwrite_cache: bool,
|
||||
}
|
||||
|
||||
impl PullCommand {
|
||||
impl ImagePullCommand {
|
||||
pub async fn run(self, mut client: ControlServiceClient<Channel>) -> Result<()> {
|
||||
let response = client
|
||||
.pull_image(PullImageRequest {
|
||||
image: self.image.clone(),
|
||||
format: match self.image_format {
|
||||
PullImageFormat::Squashfs => OciImageFormat::Squashfs.into(),
|
||||
PullImageFormat::Erofs => OciImageFormat::Erofs.into(),
|
||||
PullImageFormat::Tar => OciImageFormat::Tar.into(),
|
||||
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?;
|
@ -1,66 +1,42 @@
|
||||
pub mod attach;
|
||||
pub mod destroy;
|
||||
pub mod exec;
|
||||
pub mod identify_host;
|
||||
pub mod idm_snoop;
|
||||
pub mod launch;
|
||||
pub mod list;
|
||||
pub mod logs;
|
||||
pub mod metrics;
|
||||
pub mod pull;
|
||||
pub mod resolve;
|
||||
pub mod top;
|
||||
pub mod watch;
|
||||
pub mod device;
|
||||
pub mod host;
|
||||
pub mod image;
|
||||
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, Subcommand};
|
||||
use clap::Parser;
|
||||
use krata::{
|
||||
client::ControlClientProvider,
|
||||
events::EventStream,
|
||||
v1::control::{control_service_client::ControlServiceClient, ResolveGuestRequest},
|
||||
v1::control::{control_service_client::ControlServiceClient, ResolveZoneRequest},
|
||||
};
|
||||
use tonic::{transport::Channel, Request};
|
||||
|
||||
use self::{
|
||||
attach::AttachCommand, destroy::DestroyCommand, exec::ExecCommand,
|
||||
identify_host::IdentifyHostCommand, idm_snoop::IdmSnoopCommand, launch::LaunchCommand,
|
||||
list::ListCommand, logs::LogsCommand, metrics::MetricsCommand, pull::PullCommand,
|
||||
resolve::ResolveCommand, top::TopCommand, watch::WatchCommand,
|
||||
};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(
|
||||
version,
|
||||
about = "Control the krata hypervisor, a secure platform for running containers"
|
||||
)]
|
||||
#[command(version, about = "Control the krata isolation engine")]
|
||||
pub struct ControlCommand {
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "The connection URL to the krata hypervisor",
|
||||
help = "The connection URL to the krata isolation engine",
|
||||
default_value = "unix:///var/lib/krata/daemon.socket"
|
||||
)]
|
||||
connection: String,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
command: ControlCommands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Commands {
|
||||
Launch(LaunchCommand),
|
||||
Destroy(DestroyCommand),
|
||||
List(ListCommand),
|
||||
Attach(AttachCommand),
|
||||
Pull(PullCommand),
|
||||
Logs(LogsCommand),
|
||||
Watch(WatchCommand),
|
||||
Resolve(ResolveCommand),
|
||||
Metrics(MetricsCommand),
|
||||
IdmSnoop(IdmSnoopCommand),
|
||||
Top(TopCommand),
|
||||
IdentifyHost(IdentifyHostCommand),
|
||||
Exec(ExecCommand),
|
||||
#[derive(Parser)]
|
||||
pub enum ControlCommands {
|
||||
Zone(ZoneCommand),
|
||||
Image(ImageCommand),
|
||||
Device(DeviceCommand),
|
||||
Host(HostCommand),
|
||||
}
|
||||
|
||||
impl ControlCommand {
|
||||
@ -69,76 +45,31 @@ impl ControlCommand {
|
||||
let events = EventStream::open(client.clone()).await?;
|
||||
|
||||
match self.command {
|
||||
Commands::Launch(launch) => {
|
||||
launch.run(client, events).await?;
|
||||
}
|
||||
ControlCommands::Zone(zone) => zone.run(client, events).await,
|
||||
|
||||
Commands::Destroy(destroy) => {
|
||||
destroy.run(client, events).await?;
|
||||
}
|
||||
ControlCommands::Image(image) => image.run(client, events).await,
|
||||
|
||||
Commands::Attach(attach) => {
|
||||
attach.run(client, events).await?;
|
||||
}
|
||||
ControlCommands::Device(device) => device.run(client, events).await,
|
||||
|
||||
Commands::Logs(logs) => {
|
||||
logs.run(client, events).await?;
|
||||
}
|
||||
|
||||
Commands::List(list) => {
|
||||
list.run(client, events).await?;
|
||||
}
|
||||
|
||||
Commands::Watch(watch) => {
|
||||
watch.run(events).await?;
|
||||
}
|
||||
|
||||
Commands::Resolve(resolve) => {
|
||||
resolve.run(client).await?;
|
||||
}
|
||||
|
||||
Commands::Metrics(metrics) => {
|
||||
metrics.run(client, events).await?;
|
||||
}
|
||||
|
||||
Commands::IdmSnoop(snoop) => {
|
||||
snoop.run(client, events).await?;
|
||||
}
|
||||
|
||||
Commands::Top(top) => {
|
||||
top.run(client, events).await?;
|
||||
}
|
||||
|
||||
Commands::Pull(pull) => {
|
||||
pull.run(client).await?;
|
||||
}
|
||||
|
||||
Commands::IdentifyHost(identify) => {
|
||||
identify.run(client).await?;
|
||||
}
|
||||
|
||||
Commands::Exec(exec) => {
|
||||
exec.run(client).await?;
|
||||
}
|
||||
ControlCommands::Host(snoop) => snoop.run(client, events).await,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn resolve_guest(
|
||||
pub async fn resolve_zone(
|
||||
client: &mut ControlServiceClient<Channel>,
|
||||
name: &str,
|
||||
) -> Result<String> {
|
||||
let reply = client
|
||||
.resolve_guest(Request::new(ResolveGuestRequest {
|
||||
.resolve_zone(Request::new(ResolveZoneRequest {
|
||||
name: name.to_string(),
|
||||
}))
|
||||
.await?
|
||||
.into_inner();
|
||||
|
||||
if let Some(guest) = reply.guest {
|
||||
Ok(guest.id)
|
||||
if let Some(zone) = reply.zone {
|
||||
Ok(zone.id)
|
||||
} else {
|
||||
Err(anyhow!("unable to resolve guest '{}'", name))
|
||||
Err(anyhow!("unable to resolve zone '{}'", name))
|
||||
}
|
||||
}
|
||||
|
@ -7,27 +7,27 @@ use tonic::transport::Channel;
|
||||
|
||||
use crate::console::StdioConsoleStream;
|
||||
|
||||
use super::resolve_guest;
|
||||
use crate::cli::resolve_zone;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Attach to the guest console")]
|
||||
pub struct AttachCommand {
|
||||
#[arg(help = "Guest to attach to, either the name or the uuid")]
|
||||
guest: String,
|
||||
#[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 AttachCommand {
|
||||
impl ZoneAttachCommand {
|
||||
pub async fn run(
|
||||
self,
|
||||
mut client: ControlServiceClient<Channel>,
|
||||
events: EventStream,
|
||||
) -> Result<()> {
|
||||
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
|
||||
let input = StdioConsoleStream::stdin_stream(guest_id.clone()).await;
|
||||
let output = client.console_data(input).await?.into_inner();
|
||||
let zone_id: String = resolve_zone(&mut client, &self.zone).await?;
|
||||
let input = StdioConsoleStream::stdin_stream(zone_id.clone()).await;
|
||||
let output = client.attach_zone_console(input).await?.into_inner();
|
||||
let stdout_handle =
|
||||
tokio::task::spawn(async move { StdioConsoleStream::stdout(output).await });
|
||||
let exit_hook_task = StdioConsoleStream::guest_exit_hook(guest_id.clone(), events).await?;
|
||||
let exit_hook_task = StdioConsoleStream::zone_exit_hook(zone_id.clone(), events).await?;
|
||||
let code = select! {
|
||||
x = stdout_handle => {
|
||||
x??;
|
@ -3,10 +3,10 @@ use clap::Parser;
|
||||
use krata::{
|
||||
events::EventStream,
|
||||
v1::{
|
||||
common::GuestStatus,
|
||||
common::ZoneStatus,
|
||||
control::{
|
||||
control_service_client::ControlServiceClient, watch_events_reply::Event,
|
||||
DestroyGuestRequest,
|
||||
DestroyZoneRequest,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -14,67 +14,67 @@ use krata::{
|
||||
use log::error;
|
||||
use tonic::{transport::Channel, Request};
|
||||
|
||||
use crate::cli::resolve_guest;
|
||||
use crate::cli::resolve_zone;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Destroy a guest")]
|
||||
pub struct DestroyCommand {
|
||||
#[command(about = "Destroy a zone")]
|
||||
pub struct ZoneDestroyCommand {
|
||||
#[arg(
|
||||
short = 'W',
|
||||
long,
|
||||
help = "Wait for the destruction of the guest to complete"
|
||||
help = "Wait for the destruction of the zone to complete"
|
||||
)]
|
||||
wait: bool,
|
||||
#[arg(help = "Guest to destroy, either the name or the uuid")]
|
||||
guest: String,
|
||||
#[arg(help = "Zone to destroy, either the name or the uuid")]
|
||||
zone: String,
|
||||
}
|
||||
|
||||
impl DestroyCommand {
|
||||
impl ZoneDestroyCommand {
|
||||
pub async fn run(
|
||||
self,
|
||||
mut client: ControlServiceClient<Channel>,
|
||||
events: EventStream,
|
||||
) -> Result<()> {
|
||||
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
|
||||
let zone_id: String = resolve_zone(&mut client, &self.zone).await?;
|
||||
let _ = client
|
||||
.destroy_guest(Request::new(DestroyGuestRequest {
|
||||
guest_id: guest_id.clone(),
|
||||
.destroy_zone(Request::new(DestroyZoneRequest {
|
||||
zone_id: zone_id.clone(),
|
||||
}))
|
||||
.await?
|
||||
.into_inner();
|
||||
if self.wait {
|
||||
wait_guest_destroyed(&guest_id, events).await?;
|
||||
wait_zone_destroyed(&zone_id, events).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_guest_destroyed(id: &str, events: EventStream) -> Result<()> {
|
||||
async fn wait_zone_destroyed(id: &str, events: EventStream) -> Result<()> {
|
||||
let mut stream = events.subscribe();
|
||||
while let Ok(event) = stream.recv().await {
|
||||
let Event::GuestChanged(changed) = event;
|
||||
let Some(guest) = changed.guest else {
|
||||
let Event::ZoneChanged(changed) = event;
|
||||
let Some(zone) = changed.zone else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if guest.id != id {
|
||||
if zone.id != id {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(state) = guest.state else {
|
||||
let Some(state) = zone.state else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(ref error) = state.error_info {
|
||||
if state.status() == GuestStatus::Failed {
|
||||
if state.status() == ZoneStatus::Failed {
|
||||
error!("destroy failed: {}", error.message);
|
||||
std::process::exit(1);
|
||||
} else {
|
||||
error!("guest error: {}", error.message);
|
||||
error!("zone error: {}", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
if state.status() == GuestStatus::Destroyed {
|
||||
if state.status() == ZoneStatus::Destroyed {
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
@ -4,42 +4,42 @@ use anyhow::Result;
|
||||
|
||||
use clap::Parser;
|
||||
use krata::v1::{
|
||||
common::{GuestTaskSpec, GuestTaskSpecEnvVar},
|
||||
control::{control_service_client::ControlServiceClient, ExecGuestRequest},
|
||||
common::{ZoneTaskSpec, ZoneTaskSpecEnvVar},
|
||||
control::{control_service_client::ControlServiceClient, ExecZoneRequest},
|
||||
};
|
||||
|
||||
use tonic::{transport::Channel, Request};
|
||||
|
||||
use crate::console::StdioConsoleStream;
|
||||
|
||||
use super::resolve_guest;
|
||||
use crate::cli::resolve_zone;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Execute a command inside the guest")]
|
||||
pub struct ExecCommand {
|
||||
#[command(about = "Execute a command inside the zone")]
|
||||
pub struct ZoneExecCommand {
|
||||
#[arg[short, long, help = "Environment variables"]]
|
||||
env: Option<Vec<String>>,
|
||||
#[arg(short = 'w', long, help = "Working directory")]
|
||||
working_directory: Option<String>,
|
||||
#[arg(help = "Guest to exec inside, either the name or the uuid")]
|
||||
guest: String,
|
||||
#[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 guest"
|
||||
help = "Command to run inside the zone"
|
||||
)]
|
||||
command: Vec<String>,
|
||||
}
|
||||
|
||||
impl ExecCommand {
|
||||
impl ZoneExecCommand {
|
||||
pub async fn run(self, mut client: ControlServiceClient<Channel>) -> Result<()> {
|
||||
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
|
||||
let initial = ExecGuestRequest {
|
||||
guest_id,
|
||||
task: Some(GuestTaskSpec {
|
||||
let zone_id: String = resolve_zone(&mut client, &self.zone).await?;
|
||||
let initial = ExecZoneRequest {
|
||||
zone_id,
|
||||
task: Some(ZoneTaskSpec {
|
||||
environment: env_map(&self.env.unwrap_or_default())
|
||||
.iter()
|
||||
.map(|(key, value)| GuestTaskSpecEnvVar {
|
||||
.map(|(key, value)| ZoneTaskSpecEnvVar {
|
||||
key: key.clone(),
|
||||
value: value.clone(),
|
||||
})
|
||||
@ -52,7 +52,7 @@ impl ExecCommand {
|
||||
|
||||
let stream = StdioConsoleStream::stdin_stream_exec(initial).await;
|
||||
|
||||
let response = client.exec_guest(Request::new(stream)).await?.into_inner();
|
||||
let response = client.exec_zone(Request::new(stream)).await?.into_inner();
|
||||
|
||||
let code = StdioConsoleStream::exec_output(response).await?;
|
||||
std::process::exit(code);
|
@ -6,12 +6,12 @@ use krata::{
|
||||
events::EventStream,
|
||||
v1::{
|
||||
common::{
|
||||
guest_image_spec::Image, GuestImageSpec, GuestOciImageSpec, GuestSpec, GuestStatus,
|
||||
GuestTaskSpec, GuestTaskSpecEnvVar, OciImageFormat,
|
||||
zone_image_spec::Image, OciImageFormat, ZoneImageSpec, ZoneOciImageSpec, ZoneSpec,
|
||||
ZoneSpecDevice, ZoneStatus, ZoneTaskSpec, ZoneTaskSpecEnvVar,
|
||||
},
|
||||
control::{
|
||||
control_service_client::ControlServiceClient, watch_events_reply::Event,
|
||||
CreateGuestRequest, PullImageRequest,
|
||||
CreateZoneRequest, PullImageRequest,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -28,59 +28,58 @@ pub enum LaunchImageFormat {
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Launch a new guest")]
|
||||
pub struct LaunchCommand {
|
||||
#[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(short, long, help = "Name of the guest")]
|
||||
#[arg(long, help = "Update image on pull")]
|
||||
pull_update: bool,
|
||||
#[arg(short, long, help = "Name of the zone")]
|
||||
name: Option<String>,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
default_value_t = 1,
|
||||
help = "vCPUs available to the guest"
|
||||
)]
|
||||
#[arg(short, long, default_value_t = 1, help = "vCPUs available to the zone")]
|
||||
cpus: u32,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
default_value_t = 512,
|
||||
help = "Memory available to the guest, in megabytes"
|
||||
help = "Memory available to the zone, in megabytes"
|
||||
)]
|
||||
mem: u64,
|
||||
#[arg[short, long, help = "Environment variables set in the guest"]]
|
||||
#[arg[short = 'D', long = "device", help = "Devices to request for the zone"]]
|
||||
device: Vec<String>,
|
||||
#[arg[short, long, help = "Environment variables set in the zone"]]
|
||||
env: Option<Vec<String>>,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Attach to the guest after guest starts, implies --wait"
|
||||
help = "Attach to the zone after zone starts, implies --wait"
|
||||
)]
|
||||
attach: bool,
|
||||
#[arg(
|
||||
short = 'W',
|
||||
long,
|
||||
help = "Wait for the guest to start, implied by --attach"
|
||||
help = "Wait for the zone to start, implied by --attach"
|
||||
)]
|
||||
wait: bool,
|
||||
#[arg(short = 'k', long, help = "OCI kernel image for guest to use")]
|
||||
#[arg(short = 'k', long, help = "OCI kernel image for zone to use")]
|
||||
kernel: Option<String>,
|
||||
#[arg(short = 'I', long, help = "OCI initrd image for guest to use")]
|
||||
#[arg(short = 'I', long, help = "OCI initrd image for zone to use")]
|
||||
initrd: Option<String>,
|
||||
#[arg(short = 'w', long, help = "Working directory")]
|
||||
working_directory: Option<String>,
|
||||
#[arg(help = "Container image for guest to use")]
|
||||
#[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 guest"
|
||||
help = "Command to run inside the zone"
|
||||
)]
|
||||
command: Vec<String>,
|
||||
}
|
||||
|
||||
impl LaunchCommand {
|
||||
impl ZoneLaunchCommand {
|
||||
pub async fn run(
|
||||
self,
|
||||
mut client: ControlServiceClient<Channel>,
|
||||
@ -115,18 +114,18 @@ impl LaunchCommand {
|
||||
None
|
||||
};
|
||||
|
||||
let request = CreateGuestRequest {
|
||||
spec: Some(GuestSpec {
|
||||
let request = CreateZoneRequest {
|
||||
spec: Some(ZoneSpec {
|
||||
name: self.name.unwrap_or_default(),
|
||||
image: Some(image),
|
||||
kernel,
|
||||
initrd,
|
||||
vcpus: self.cpus,
|
||||
mem: self.mem,
|
||||
task: Some(GuestTaskSpec {
|
||||
task: Some(ZoneTaskSpec {
|
||||
environment: env_map(&self.env.unwrap_or_default())
|
||||
.iter()
|
||||
.map(|(key, value)| GuestTaskSpecEnvVar {
|
||||
.map(|(key, value)| ZoneTaskSpecEnvVar {
|
||||
key: key.clone(),
|
||||
value: value.clone(),
|
||||
})
|
||||
@ -135,24 +134,29 @@ impl LaunchCommand {
|
||||
working_directory: self.working_directory.unwrap_or_default(),
|
||||
}),
|
||||
annotations: vec![],
|
||||
devices: self
|
||||
.device
|
||||
.iter()
|
||||
.map(|name| ZoneSpecDevice { name: name.clone() })
|
||||
.collect(),
|
||||
}),
|
||||
};
|
||||
let response = client
|
||||
.create_guest(Request::new(request))
|
||||
.create_zone(Request::new(request))
|
||||
.await?
|
||||
.into_inner();
|
||||
let id = response.guest_id;
|
||||
let id = response.zone_id;
|
||||
|
||||
if self.wait || self.attach {
|
||||
wait_guest_started(&id, events.clone()).await?;
|
||||
wait_zone_started(&id, events.clone()).await?;
|
||||
}
|
||||
|
||||
let code = if self.attach {
|
||||
let input = StdioConsoleStream::stdin_stream(id.clone()).await;
|
||||
let output = client.console_data(input).await?.into_inner();
|
||||
let output = client.attach_zone_console(input).await?.into_inner();
|
||||
let stdout_handle =
|
||||
tokio::task::spawn(async move { StdioConsoleStream::stdout(output).await });
|
||||
let exit_hook_task = StdioConsoleStream::guest_exit_hook(id.clone(), events).await?;
|
||||
let exit_hook_task = StdioConsoleStream::zone_exit_hook(id.clone(), events).await?;
|
||||
select! {
|
||||
x = stdout_handle => {
|
||||
x??;
|
||||
@ -173,17 +177,18 @@ impl LaunchCommand {
|
||||
client: &mut ControlServiceClient<Channel>,
|
||||
image: &str,
|
||||
format: OciImageFormat,
|
||||
) -> Result<GuestImageSpec> {
|
||||
) -> Result<ZoneImageSpec> {
|
||||
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(GuestImageSpec {
|
||||
image: Some(Image::Oci(GuestOciImageSpec {
|
||||
Ok(ZoneImageSpec {
|
||||
image: Some(Image::Oci(ZoneOciImageSpec {
|
||||
digest: reply.digest,
|
||||
format: reply.format,
|
||||
})),
|
||||
@ -191,38 +196,38 @@ impl LaunchCommand {
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_guest_started(id: &str, events: EventStream) -> Result<()> {
|
||||
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::GuestChanged(changed) => {
|
||||
let Some(guest) = changed.guest else {
|
||||
Event::ZoneChanged(changed) => {
|
||||
let Some(zone) = changed.zone else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if guest.id != id {
|
||||
if zone.id != id {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(state) = guest.state else {
|
||||
let Some(state) = zone.state else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(ref error) = state.error_info {
|
||||
if state.status() == GuestStatus::Failed {
|
||||
if state.status() == ZoneStatus::Failed {
|
||||
error!("launch failed: {}", error.message);
|
||||
std::process::exit(1);
|
||||
} else {
|
||||
error!("guest error: {}", error.message);
|
||||
error!("zone error: {}", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
if state.status() == GuestStatus::Destroyed {
|
||||
error!("guest destroyed");
|
||||
if state.status() == ZoneStatus::Destroyed {
|
||||
error!("zone destroyed");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if state.status() == GuestStatus::Started {
|
||||
if state.status() == ZoneStatus::Started {
|
||||
break;
|
||||
}
|
||||
}
|
@ -4,9 +4,9 @@ use comfy_table::{presets::UTF8_FULL_CONDENSED, Cell, Color, Table};
|
||||
use krata::{
|
||||
events::EventStream,
|
||||
v1::{
|
||||
common::{Guest, GuestStatus},
|
||||
common::{Zone, ZoneStatus},
|
||||
control::{
|
||||
control_service_client::ControlServiceClient, ListGuestsRequest, ResolveGuestRequest,
|
||||
control_service_client::ControlServiceClient, ListZonesRequest, ResolveZoneRequest,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -14,10 +14,10 @@ use krata::{
|
||||
use serde_json::Value;
|
||||
use tonic::{transport::Channel, Request};
|
||||
|
||||
use crate::format::{guest_simple_line, guest_status_text, kv2line, proto2dynamic, proto2kv};
|
||||
use crate::format::{kv2line, proto2dynamic, proto2kv, zone_simple_line, zone_status_text};
|
||||
|
||||
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
|
||||
enum ListFormat {
|
||||
enum ZoneListFormat {
|
||||
Table,
|
||||
Json,
|
||||
JsonPretty,
|
||||
@ -28,41 +28,39 @@ enum ListFormat {
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "List the guests on the hypervisor")]
|
||||
pub struct ListCommand {
|
||||
#[command(about = "List the zones on the isolation engine")]
|
||||
pub struct ZoneListCommand {
|
||||
#[arg(short, long, default_value = "table", help = "Output format")]
|
||||
format: ListFormat,
|
||||
#[arg(help = "Limit to a single guest, either the name or the uuid")]
|
||||
guest: Option<String>,
|
||||
format: ZoneListFormat,
|
||||
#[arg(help = "Limit to a single zone, either the name or the uuid")]
|
||||
zone: Option<String>,
|
||||
}
|
||||
|
||||
impl ListCommand {
|
||||
impl ZoneListCommand {
|
||||
pub async fn run(
|
||||
self,
|
||||
mut client: ControlServiceClient<Channel>,
|
||||
_events: EventStream,
|
||||
) -> Result<()> {
|
||||
let mut guests = if let Some(ref guest) = self.guest {
|
||||
let mut zones = if let Some(ref zone) = self.zone {
|
||||
let reply = client
|
||||
.resolve_guest(Request::new(ResolveGuestRequest {
|
||||
name: guest.clone(),
|
||||
}))
|
||||
.resolve_zone(Request::new(ResolveZoneRequest { name: zone.clone() }))
|
||||
.await?
|
||||
.into_inner();
|
||||
if let Some(guest) = reply.guest {
|
||||
vec![guest]
|
||||
if let Some(zone) = reply.zone {
|
||||
vec![zone]
|
||||
} else {
|
||||
return Err(anyhow!("unable to resolve guest '{}'", guest));
|
||||
return Err(anyhow!("unable to resolve zone '{}'", zone));
|
||||
}
|
||||
} else {
|
||||
client
|
||||
.list_guests(Request::new(ListGuestsRequest {}))
|
||||
.list_zones(Request::new(ListZonesRequest {}))
|
||||
.await?
|
||||
.into_inner()
|
||||
.guests
|
||||
.zones
|
||||
};
|
||||
|
||||
guests.sort_by(|a, b| {
|
||||
zones.sort_by(|a, b| {
|
||||
a.spec
|
||||
.as_ref()
|
||||
.map(|x| x.name.as_str())
|
||||
@ -71,26 +69,26 @@ impl ListCommand {
|
||||
});
|
||||
|
||||
match self.format {
|
||||
ListFormat::Table => {
|
||||
self.print_guest_table(guests)?;
|
||||
ZoneListFormat::Table => {
|
||||
self.print_zone_table(zones)?;
|
||||
}
|
||||
|
||||
ListFormat::Simple => {
|
||||
for guest in guests {
|
||||
println!("{}", guest_simple_line(&guest));
|
||||
ZoneListFormat::Simple => {
|
||||
for zone in zones {
|
||||
println!("{}", zone_simple_line(&zone));
|
||||
}
|
||||
}
|
||||
|
||||
ListFormat::Json | ListFormat::JsonPretty | ListFormat::Yaml => {
|
||||
ZoneListFormat::Json | ZoneListFormat::JsonPretty | ZoneListFormat::Yaml => {
|
||||
let mut values = Vec::new();
|
||||
for guest in guests {
|
||||
let message = proto2dynamic(guest)?;
|
||||
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 == ListFormat::JsonPretty {
|
||||
let encoded = if self.format == ZoneListFormat::JsonPretty {
|
||||
serde_json::to_string_pretty(&value)?
|
||||
} else if self.format == ListFormat::Yaml {
|
||||
} else if self.format == ZoneListFormat::Yaml {
|
||||
serde_yaml::to_string(&value)?
|
||||
} else {
|
||||
serde_json::to_string(&value)?
|
||||
@ -98,65 +96,63 @@ impl ListCommand {
|
||||
println!("{}", encoded.trim());
|
||||
}
|
||||
|
||||
ListFormat::Jsonl => {
|
||||
for guest in guests {
|
||||
let message = proto2dynamic(guest)?;
|
||||
ZoneListFormat::Jsonl => {
|
||||
for zone in zones {
|
||||
let message = proto2dynamic(zone)?;
|
||||
println!("{}", serde_json::to_string(&message)?);
|
||||
}
|
||||
}
|
||||
|
||||
ListFormat::KeyValue => {
|
||||
self.print_key_value(guests)?;
|
||||
ZoneListFormat::KeyValue => {
|
||||
self.print_key_value(zones)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_guest_table(&self, guests: Vec<Guest>) -> Result<()> {
|
||||
fn print_zone_table(&self, zones: Vec<Zone>) -> 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", "status", "ipv4", "ipv6"]);
|
||||
for guest in guests {
|
||||
let ipv4 = guest
|
||||
for zone in zones {
|
||||
let ipv4 = zone
|
||||
.state
|
||||
.as_ref()
|
||||
.and_then(|x| x.network.as_ref())
|
||||
.map(|x| x.guest_ipv4.as_str())
|
||||
.map(|x| x.zone_ipv4.as_str())
|
||||
.unwrap_or("n/a");
|
||||
let ipv6 = guest
|
||||
let ipv6 = zone
|
||||
.state
|
||||
.as_ref()
|
||||
.and_then(|x| x.network.as_ref())
|
||||
.map(|x| x.guest_ipv6.as_str())
|
||||
.map(|x| x.zone_ipv6.as_str())
|
||||
.unwrap_or("n/a");
|
||||
let Some(spec) = guest.spec else {
|
||||
let Some(spec) = zone.spec else {
|
||||
continue;
|
||||
};
|
||||
let status = guest.state.as_ref().cloned().unwrap_or_default().status();
|
||||
let status_text = guest_status_text(status);
|
||||
let status = zone.state.as_ref().cloned().unwrap_or_default().status();
|
||||
let status_text = zone_status_text(status);
|
||||
|
||||
let status_color = match status {
|
||||
GuestStatus::Destroyed | GuestStatus::Failed => Color::Red,
|
||||
GuestStatus::Destroying | GuestStatus::Exited | GuestStatus::Starting => {
|
||||
Color::Yellow
|
||||
}
|
||||
GuestStatus::Started => Color::Green,
|
||||
ZoneStatus::Destroyed | ZoneStatus::Failed => Color::Red,
|
||||
ZoneStatus::Destroying | ZoneStatus::Exited | ZoneStatus::Starting => Color::Yellow,
|
||||
ZoneStatus::Started => Color::Green,
|
||||
_ => Color::Reset,
|
||||
};
|
||||
|
||||
table.add_row(vec![
|
||||
Cell::new(spec.name),
|
||||
Cell::new(guest.id),
|
||||
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.guest.is_none() {
|
||||
println!("no guests have been launched");
|
||||
if self.zone.is_none() {
|
||||
println!("no zones have been launched");
|
||||
}
|
||||
} else {
|
||||
println!("{}", table);
|
||||
@ -164,9 +160,9 @@ impl ListCommand {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_key_value(&self, guests: Vec<Guest>) -> Result<()> {
|
||||
for guest in guests {
|
||||
let kvs = proto2kv(guest)?;
|
||||
fn print_key_value(&self, zones: Vec<Zone>) -> Result<()> {
|
||||
for zone in zones {
|
||||
let kvs = proto2kv(zone)?;
|
||||
println!("{}", kv2line(kvs),);
|
||||
}
|
||||
Ok(())
|
@ -3,7 +3,7 @@ use async_stream::stream;
|
||||
use clap::Parser;
|
||||
use krata::{
|
||||
events::EventStream,
|
||||
v1::control::{control_service_client::ControlServiceClient, ConsoleDataRequest},
|
||||
v1::control::{control_service_client::ControlServiceClient, ZoneConsoleRequest},
|
||||
};
|
||||
|
||||
use tokio::select;
|
||||
@ -12,39 +12,39 @@ use tonic::transport::Channel;
|
||||
|
||||
use crate::console::StdioConsoleStream;
|
||||
|
||||
use super::resolve_guest;
|
||||
use crate::cli::resolve_zone;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "View the logs of a guest")]
|
||||
pub struct LogsCommand {
|
||||
#[arg(short, long, help = "Follow output from the guest")]
|
||||
#[command(about = "View the logs of a zone")]
|
||||
pub struct ZoneLogsCommand {
|
||||
#[arg(short, long, help = "Follow output from the zone")]
|
||||
follow: bool,
|
||||
#[arg(help = "Guest to show logs for, either the name or the uuid")]
|
||||
guest: String,
|
||||
#[arg(help = "Zone to show logs for, either the name or the uuid")]
|
||||
zone: String,
|
||||
}
|
||||
|
||||
impl LogsCommand {
|
||||
impl ZoneLogsCommand {
|
||||
pub async fn run(
|
||||
self,
|
||||
mut client: ControlServiceClient<Channel>,
|
||||
events: EventStream,
|
||||
) -> Result<()> {
|
||||
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
|
||||
let guest_id_stream = guest_id.clone();
|
||||
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 ConsoleDataRequest { guest_id: guest_id_stream, data: Vec::new() };
|
||||
yield ZoneConsoleRequest { zone_id: zone_id_stream, data: Vec::new() };
|
||||
if follow {
|
||||
let mut pending = pending::<ConsoleDataRequest>();
|
||||
let mut pending = pending::<ZoneConsoleRequest>();
|
||||
while let Some(x) = pending.next().await {
|
||||
yield x;
|
||||
}
|
||||
}
|
||||
};
|
||||
let output = client.console_data(input).await?.into_inner();
|
||||
let output = client.attach_zone_console(input).await?.into_inner();
|
||||
let stdout_handle =
|
||||
tokio::task::spawn(async move { StdioConsoleStream::stdout(output).await });
|
||||
let exit_hook_task = StdioConsoleStream::guest_exit_hook(guest_id.clone(), events).await?;
|
||||
let exit_hook_task = StdioConsoleStream::zone_exit_hook(zone_id.clone(), events).await?;
|
||||
let code = select! {
|
||||
x = stdout_handle => {
|
||||
x??;
|
@ -3,8 +3,8 @@ use clap::{Parser, ValueEnum};
|
||||
use krata::{
|
||||
events::EventStream,
|
||||
v1::{
|
||||
common::GuestMetricNode,
|
||||
control::{control_service_client::ControlServiceClient, ReadGuestMetricsRequest},
|
||||
common::ZoneMetricNode,
|
||||
control::{control_service_client::ControlServiceClient, ReadZoneMetricsRequest},
|
||||
},
|
||||
};
|
||||
|
||||
@ -12,10 +12,10 @@ use tonic::transport::Channel;
|
||||
|
||||
use crate::format::{kv2line, metrics_flat, metrics_tree, proto2dynamic};
|
||||
|
||||
use super::resolve_guest;
|
||||
use crate::cli::resolve_zone;
|
||||
|
||||
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
|
||||
enum MetricsFormat {
|
||||
enum ZoneMetricsFormat {
|
||||
Tree,
|
||||
Json,
|
||||
JsonPretty,
|
||||
@ -24,37 +24,37 @@ enum MetricsFormat {
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Read metrics from the guest")]
|
||||
pub struct MetricsCommand {
|
||||
#[command(about = "Read metrics from the zone")]
|
||||
pub struct ZoneMetricsCommand {
|
||||
#[arg(short, long, default_value = "tree", help = "Output format")]
|
||||
format: MetricsFormat,
|
||||
#[arg(help = "Guest to read metrics for, either the name or the uuid")]
|
||||
guest: String,
|
||||
format: ZoneMetricsFormat,
|
||||
#[arg(help = "Zone to read metrics for, either the name or the uuid")]
|
||||
zone: String,
|
||||
}
|
||||
|
||||
impl MetricsCommand {
|
||||
impl ZoneMetricsCommand {
|
||||
pub async fn run(
|
||||
self,
|
||||
mut client: ControlServiceClient<Channel>,
|
||||
_events: EventStream,
|
||||
) -> Result<()> {
|
||||
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
|
||||
let zone_id: String = resolve_zone(&mut client, &self.zone).await?;
|
||||
let root = client
|
||||
.read_guest_metrics(ReadGuestMetricsRequest { guest_id })
|
||||
.read_zone_metrics(ReadZoneMetricsRequest { zone_id })
|
||||
.await?
|
||||
.into_inner()
|
||||
.root
|
||||
.unwrap_or_default();
|
||||
match self.format {
|
||||
MetricsFormat::Tree => {
|
||||
ZoneMetricsFormat::Tree => {
|
||||
self.print_metrics_tree(root)?;
|
||||
}
|
||||
|
||||
MetricsFormat::Json | MetricsFormat::JsonPretty | MetricsFormat::Yaml => {
|
||||
ZoneMetricsFormat::Json | ZoneMetricsFormat::JsonPretty | ZoneMetricsFormat::Yaml => {
|
||||
let value = serde_json::to_value(proto2dynamic(root)?)?;
|
||||
let encoded = if self.format == MetricsFormat::JsonPretty {
|
||||
let encoded = if self.format == ZoneMetricsFormat::JsonPretty {
|
||||
serde_json::to_string_pretty(&value)?
|
||||
} else if self.format == MetricsFormat::Yaml {
|
||||
} else if self.format == ZoneMetricsFormat::Yaml {
|
||||
serde_yaml::to_string(&value)?
|
||||
} else {
|
||||
serde_json::to_string(&value)?
|
||||
@ -62,7 +62,7 @@ impl MetricsCommand {
|
||||
println!("{}", encoded.trim());
|
||||
}
|
||||
|
||||
MetricsFormat::KeyValue => {
|
||||
ZoneMetricsFormat::KeyValue => {
|
||||
self.print_key_value(root)?;
|
||||
}
|
||||
}
|
||||
@ -70,12 +70,12 @@ impl MetricsCommand {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_metrics_tree(&self, root: GuestMetricNode) -> Result<()> {
|
||||
fn print_metrics_tree(&self, root: ZoneMetricNode) -> Result<()> {
|
||||
print!("{}", metrics_tree(root));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_key_value(&self, metrics: GuestMetricNode) -> Result<()> {
|
||||
fn print_key_value(&self, metrics: ZoneMetricNode) -> Result<()> {
|
||||
let kvs = metrics_flat(metrics);
|
||||
println!("{}", kv2line(kvs));
|
||||
Ok(())
|
89
crates/ctl/src/cli/zone/mod.rs
Normal file
89
crates/ctl/src/cli/zone/mod.rs
Normal file
@ -0,0 +1,89 @@
|
||||
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::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 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<Channel>,
|
||||
events: EventStream,
|
||||
) -> Result<()> {
|
||||
self.subcommand.run(client, events).await
|
||||
}
|
||||
}
|
||||
|
||||
#[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),
|
||||
}
|
||||
|
||||
impl ZoneCommands {
|
||||
pub async fn run(
|
||||
self,
|
||||
client: ControlServiceClient<Channel>,
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,26 +1,26 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use krata::v1::control::{control_service_client::ControlServiceClient, ResolveGuestRequest};
|
||||
use krata::v1::control::{control_service_client::ControlServiceClient, ResolveZoneRequest};
|
||||
|
||||
use tonic::{transport::Channel, Request};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Resolve a guest name to a uuid")]
|
||||
pub struct ResolveCommand {
|
||||
#[arg(help = "Guest name")]
|
||||
guest: String,
|
||||
#[command(about = "Resolve a zone name to a uuid")]
|
||||
pub struct ZoneResolveCommand {
|
||||
#[arg(help = "Zone name")]
|
||||
zone: String,
|
||||
}
|
||||
|
||||
impl ResolveCommand {
|
||||
impl ZoneResolveCommand {
|
||||
pub async fn run(self, mut client: ControlServiceClient<Channel>) -> Result<()> {
|
||||
let reply = client
|
||||
.resolve_guest(Request::new(ResolveGuestRequest {
|
||||
name: self.guest.clone(),
|
||||
.resolve_zone(Request::new(ResolveZoneRequest {
|
||||
name: self.zone.clone(),
|
||||
}))
|
||||
.await?
|
||||
.into_inner();
|
||||
if let Some(guest) = reply.guest {
|
||||
println!("{}", guest.id);
|
||||
if let Some(zone) = reply.zone {
|
||||
println!("{}", zone.id);
|
||||
} else {
|
||||
std::process::exit(1);
|
||||
}
|
@ -24,19 +24,19 @@ use ratatui::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
format::guest_status_text,
|
||||
format::zone_status_text,
|
||||
metrics::{
|
||||
lookup_metric_value, MultiMetricCollector, MultiMetricCollectorHandle, MultiMetricState,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Dashboard for running guests")]
|
||||
pub struct TopCommand {}
|
||||
#[command(about = "Dashboard for running zones")]
|
||||
pub struct ZoneTopCommand {}
|
||||
|
||||
pub type Tui = Terminal<CrosstermBackend<Stdout>>;
|
||||
|
||||
impl TopCommand {
|
||||
impl ZoneTopCommand {
|
||||
pub async fn run(
|
||||
self,
|
||||
client: ControlServiceClient<Channel>,
|
||||
@ -44,14 +44,14 @@ impl TopCommand {
|
||||
) -> Result<()> {
|
||||
let collector = MultiMetricCollector::new(client, events, Duration::from_millis(200))?;
|
||||
let collector = collector.launch().await?;
|
||||
let mut tui = TopCommand::init()?;
|
||||
let mut app = TopApp {
|
||||
metrics: MultiMetricState { guests: vec![] },
|
||||
let mut tui = ZoneTopCommand::init()?;
|
||||
let mut app = ZoneTopApp {
|
||||
metrics: MultiMetricState { zones: vec![] },
|
||||
exit: false,
|
||||
table: TableState::new(),
|
||||
};
|
||||
app.run(collector, &mut tui).await?;
|
||||
TopCommand::restore()?;
|
||||
ZoneTopCommand::restore()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -68,13 +68,13 @@ impl TopCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TopApp {
|
||||
pub struct ZoneTopApp {
|
||||
table: TableState,
|
||||
metrics: MultiMetricState,
|
||||
exit: bool,
|
||||
}
|
||||
|
||||
impl TopApp {
|
||||
impl ZoneTopApp {
|
||||
pub async fn run(
|
||||
&mut self,
|
||||
mut collector: MultiMetricCollectorHandle,
|
||||
@ -136,9 +136,9 @@ impl TopApp {
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &mut TopApp {
|
||||
impl Widget for &mut ZoneTopApp {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let title = Title::from(" krata hypervisor ".bold());
|
||||
let title = Title::from(" krata isolation engine ".bold());
|
||||
let instructions = Title::from(vec![" Quit ".into(), "<Q> ".blue().bold()]);
|
||||
let block = Block::default()
|
||||
.title(title.alignment(Alignment::Center))
|
||||
@ -152,12 +152,12 @@ impl Widget for &mut TopApp {
|
||||
|
||||
let mut rows = vec![];
|
||||
|
||||
for ms in &self.metrics.guests {
|
||||
let Some(ref spec) = ms.guest.spec else {
|
||||
for ms in &self.metrics.zones {
|
||||
let Some(ref spec) = ms.zone.spec else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(ref state) = ms.guest.state else {
|
||||
let Some(ref state) = ms.zone.state else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@ -176,8 +176,8 @@ impl Widget for &mut TopApp {
|
||||
|
||||
let row = Row::new(vec![
|
||||
spec.name.clone(),
|
||||
ms.guest.id.clone(),
|
||||
guest_status_text(state.status()),
|
||||
ms.zone.id.clone(),
|
||||
zone_status_text(state.status()),
|
||||
memory_total.unwrap_or_default(),
|
||||
memory_used.unwrap_or_default(),
|
||||
memory_free.unwrap_or_default(),
|
@ -2,53 +2,48 @@ use anyhow::Result;
|
||||
use clap::{Parser, ValueEnum};
|
||||
use krata::{
|
||||
events::EventStream,
|
||||
v1::{common::Guest, control::watch_events_reply::Event},
|
||||
v1::{common::Zone, control::watch_events_reply::Event},
|
||||
};
|
||||
use prost_reflect::ReflectMessage;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::format::{guest_simple_line, kv2line, proto2dynamic, proto2kv};
|
||||
use crate::format::{kv2line, proto2dynamic, proto2kv, zone_simple_line};
|
||||
|
||||
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
|
||||
enum WatchFormat {
|
||||
enum ZoneWatchFormat {
|
||||
Simple,
|
||||
Json,
|
||||
KeyValue,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Watch for guest changes")]
|
||||
pub struct WatchCommand {
|
||||
#[command(about = "Watch for zone changes")]
|
||||
pub struct ZoneWatchCommand {
|
||||
#[arg(short, long, default_value = "simple", help = "Output format")]
|
||||
format: WatchFormat,
|
||||
format: ZoneWatchFormat,
|
||||
}
|
||||
|
||||
impl WatchCommand {
|
||||
impl ZoneWatchCommand {
|
||||
pub async fn run(self, events: EventStream) -> Result<()> {
|
||||
let mut stream = events.subscribe();
|
||||
loop {
|
||||
let event = stream.recv().await?;
|
||||
|
||||
let Event::GuestChanged(changed) = event;
|
||||
let guest = changed.guest.clone();
|
||||
self.print_event("guest.changed", changed, guest)?;
|
||||
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,
|
||||
guest: Option<Guest>,
|
||||
) -> Result<()> {
|
||||
fn print_event(&self, typ: &str, event: impl ReflectMessage, zone: Option<Zone>) -> Result<()> {
|
||||
match self.format {
|
||||
WatchFormat::Simple => {
|
||||
if let Some(guest) = guest {
|
||||
println!("{}", guest_simple_line(&guest));
|
||||
ZoneWatchFormat::Simple => {
|
||||
if let Some(zone) = zone {
|
||||
println!("{}", zone_simple_line(&zone));
|
||||
}
|
||||
}
|
||||
|
||||
WatchFormat::Json => {
|
||||
ZoneWatchFormat::Json => {
|
||||
let message = proto2dynamic(event)?;
|
||||
let mut value = serde_json::to_value(&message)?;
|
||||
if let Value::Object(ref mut map) = value {
|
||||
@ -57,7 +52,7 @@ impl WatchCommand {
|
||||
println!("{}", serde_json::to_string(&value)?);
|
||||
}
|
||||
|
||||
WatchFormat::KeyValue => {
|
||||
ZoneWatchFormat::KeyValue => {
|
||||
let mut map = proto2kv(event)?;
|
||||
map.insert("event.type".to_string(), typ.to_string());
|
||||
println!("{}", kv2line(map),);
|
@ -7,10 +7,10 @@ use crossterm::{
|
||||
use krata::{
|
||||
events::EventStream,
|
||||
v1::{
|
||||
common::GuestStatus,
|
||||
common::ZoneStatus,
|
||||
control::{
|
||||
watch_events_reply::Event, ConsoleDataReply, ConsoleDataRequest, ExecGuestReply,
|
||||
ExecGuestRequest,
|
||||
watch_events_reply::Event, ExecZoneReply, ExecZoneRequest, ZoneConsoleReply,
|
||||
ZoneConsoleRequest,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -25,10 +25,10 @@ use tonic::Streaming;
|
||||
pub struct StdioConsoleStream;
|
||||
|
||||
impl StdioConsoleStream {
|
||||
pub async fn stdin_stream(guest: String) -> impl Stream<Item = ConsoleDataRequest> {
|
||||
pub async fn stdin_stream(zone: String) -> impl Stream<Item = ZoneConsoleRequest> {
|
||||
let mut stdin = stdin();
|
||||
stream! {
|
||||
yield ConsoleDataRequest { guest_id: guest, data: vec![] };
|
||||
yield ZoneConsoleRequest { zone_id: zone, data: vec![] };
|
||||
|
||||
let mut buffer = vec![0u8; 60];
|
||||
loop {
|
||||
@ -43,14 +43,14 @@ impl StdioConsoleStream {
|
||||
if size == 1 && buffer[0] == 0x1d {
|
||||
break;
|
||||
}
|
||||
yield ConsoleDataRequest { guest_id: String::default(), data };
|
||||
yield ZoneConsoleRequest { zone_id: String::default(), data };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn stdin_stream_exec(
|
||||
initial: ExecGuestRequest,
|
||||
) -> impl Stream<Item = ExecGuestRequest> {
|
||||
initial: ExecZoneRequest,
|
||||
) -> impl Stream<Item = ExecZoneRequest> {
|
||||
let mut stdin = stdin();
|
||||
stream! {
|
||||
yield initial;
|
||||
@ -68,12 +68,12 @@ impl StdioConsoleStream {
|
||||
if size == 1 && buffer[0] == 0x1d {
|
||||
break;
|
||||
}
|
||||
yield ExecGuestRequest { guest_id: String::default(), task: None, data };
|
||||
yield ExecZoneRequest { zone_id: String::default(), task: None, data };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn stdout(mut stream: Streaming<ConsoleDataReply>) -> Result<()> {
|
||||
pub async fn stdout(mut stream: Streaming<ZoneConsoleReply>) -> Result<()> {
|
||||
if stdin().is_tty() {
|
||||
enable_raw_mode()?;
|
||||
StdioConsoleStream::register_terminal_restore_hook()?;
|
||||
@ -90,7 +90,7 @@ impl StdioConsoleStream {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn exec_output(mut stream: Streaming<ExecGuestReply>) -> Result<i32> {
|
||||
pub async fn exec_output(mut stream: Streaming<ExecZoneReply>) -> Result<i32> {
|
||||
let mut stdout = stdout();
|
||||
let mut stderr = stderr();
|
||||
while let Some(reply) = stream.next().await {
|
||||
@ -106,33 +106,33 @@ impl StdioConsoleStream {
|
||||
}
|
||||
|
||||
if reply.exited {
|
||||
if reply.error.is_empty() {
|
||||
return Ok(reply.exit_code);
|
||||
return if reply.error.is_empty() {
|
||||
Ok(reply.exit_code)
|
||||
} else {
|
||||
return Err(anyhow!("exec failed: {}", reply.error));
|
||||
}
|
||||
Err(anyhow!("exec failed: {}", reply.error))
|
||||
};
|
||||
}
|
||||
}
|
||||
Ok(-1)
|
||||
}
|
||||
|
||||
pub async fn guest_exit_hook(
|
||||
pub async fn zone_exit_hook(
|
||||
id: String,
|
||||
events: EventStream,
|
||||
) -> Result<JoinHandle<Option<i32>>> {
|
||||
Ok(tokio::task::spawn(async move {
|
||||
let mut stream = events.subscribe();
|
||||
while let Ok(event) = stream.recv().await {
|
||||
let Event::GuestChanged(changed) = event;
|
||||
let Some(guest) = changed.guest else {
|
||||
let Event::ZoneChanged(changed) = event;
|
||||
let Some(zone) = changed.zone else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(state) = guest.state else {
|
||||
let Some(state) = zone.state else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if guest.id != id {
|
||||
if zone.id != id {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -141,7 +141,7 @@ impl StdioConsoleStream {
|
||||
}
|
||||
|
||||
let status = state.status();
|
||||
if status == GuestStatus::Destroying || status == GuestStatus::Destroyed {
|
||||
if status == ZoneStatus::Destroying || status == ZoneStatus::Destroyed {
|
||||
return Some(10);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use std::{collections::HashMap, time::Duration};
|
||||
use anyhow::Result;
|
||||
use fancy_duration::FancyDuration;
|
||||
use human_bytes::human_bytes;
|
||||
use krata::v1::common::{Guest, GuestMetricFormat, GuestMetricNode, GuestStatus};
|
||||
use krata::v1::common::{Zone, ZoneMetricFormat, ZoneMetricNode, ZoneStatus};
|
||||
use prost_reflect::{DynamicMessage, ReflectMessage};
|
||||
use prost_types::Value;
|
||||
use termtree::Tree;
|
||||
@ -75,32 +75,31 @@ pub fn kv2line(map: HashMap<String, String>) -> String {
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
pub fn guest_status_text(status: GuestStatus) -> String {
|
||||
pub fn zone_status_text(status: ZoneStatus) -> String {
|
||||
match status {
|
||||
GuestStatus::Starting => "starting",
|
||||
GuestStatus::Started => "started",
|
||||
GuestStatus::Destroying => "destroying",
|
||||
GuestStatus::Destroyed => "destroyed",
|
||||
GuestStatus::Exited => "exited",
|
||||
GuestStatus::Failed => "failed",
|
||||
ZoneStatus::Starting => "starting",
|
||||
ZoneStatus::Started => "started",
|
||||
ZoneStatus::Destroying => "destroying",
|
||||
ZoneStatus::Destroyed => "destroyed",
|
||||
ZoneStatus::Exited => "exited",
|
||||
ZoneStatus::Failed => "failed",
|
||||
_ => "unknown",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn guest_simple_line(guest: &Guest) -> String {
|
||||
let state = guest_status_text(
|
||||
guest
|
||||
.state
|
||||
pub fn zone_simple_line(zone: &Zone) -> String {
|
||||
let state = zone_status_text(
|
||||
zone.state
|
||||
.as_ref()
|
||||
.map(|x| x.status())
|
||||
.unwrap_or(GuestStatus::Unknown),
|
||||
.unwrap_or(ZoneStatus::Unknown),
|
||||
);
|
||||
let name = guest.spec.as_ref().map(|x| x.name.as_str()).unwrap_or("");
|
||||
let network = guest.state.as_ref().and_then(|x| x.network.as_ref());
|
||||
let ipv4 = network.map(|x| x.guest_ipv4.as_str()).unwrap_or("");
|
||||
let ipv6 = network.map(|x| x.guest_ipv6.as_str()).unwrap_or("");
|
||||
format!("{}\t{}\t{}\t{}\t{}", guest.id, state, name, ipv4, ipv6)
|
||||
let name = zone.spec.as_ref().map(|x| x.name.as_str()).unwrap_or("");
|
||||
let network = zone.state.as_ref().and_then(|x| x.network.as_ref());
|
||||
let ipv4 = network.map(|x| x.zone_ipv4.as_str()).unwrap_or("");
|
||||
let ipv6 = network.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 {
|
||||
@ -116,18 +115,18 @@ fn metrics_value_numeric(value: Value) -> f64 {
|
||||
string.parse::<f64>().ok().unwrap_or(f64::NAN)
|
||||
}
|
||||
|
||||
pub fn metrics_value_pretty(value: Value, format: GuestMetricFormat) -> String {
|
||||
pub fn metrics_value_pretty(value: Value, format: ZoneMetricFormat) -> String {
|
||||
match format {
|
||||
GuestMetricFormat::Bytes => human_bytes(metrics_value_numeric(value)),
|
||||
GuestMetricFormat::Integer => (metrics_value_numeric(value) as u64).to_string(),
|
||||
GuestMetricFormat::DurationSeconds => {
|
||||
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: GuestMetricNode, map: &mut HashMap<String, String>) {
|
||||
fn metrics_flat_internal(prefix: &str, node: ZoneMetricNode, map: &mut HashMap<String, String>) {
|
||||
if let Some(value) = node.value {
|
||||
map.insert(prefix.to_string(), metrics_value_string(value));
|
||||
}
|
||||
@ -142,13 +141,13 @@ fn metrics_flat_internal(prefix: &str, node: GuestMetricNode, map: &mut HashMap<
|
||||
}
|
||||
}
|
||||
|
||||
pub fn metrics_flat(root: GuestMetricNode) -> HashMap<String, String> {
|
||||
pub fn metrics_flat(root: ZoneMetricNode) -> HashMap<String, String> {
|
||||
let mut map = HashMap::new();
|
||||
metrics_flat_internal("", root, &mut map);
|
||||
map
|
||||
}
|
||||
|
||||
pub fn metrics_tree(node: GuestMetricNode) -> Tree<String> {
|
||||
pub fn metrics_tree(node: ZoneMetricNode) -> Tree<String> {
|
||||
let mut name = node.name.to_string();
|
||||
let format = node.format();
|
||||
if let Some(value) = node.value {
|
||||
|
@ -2,10 +2,10 @@ use anyhow::Result;
|
||||
use krata::{
|
||||
events::EventStream,
|
||||
v1::{
|
||||
common::{Guest, GuestMetricNode, GuestStatus},
|
||||
common::{Zone, ZoneMetricNode, ZoneStatus},
|
||||
control::{
|
||||
control_service_client::ControlServiceClient, watch_events_reply::Event,
|
||||
ListGuestsRequest, ReadGuestMetricsRequest,
|
||||
ListZonesRequest, ReadZoneMetricsRequest,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -22,12 +22,12 @@ use tonic::transport::Channel;
|
||||
use crate::format::metrics_value_pretty;
|
||||
|
||||
pub struct MetricState {
|
||||
pub guest: Guest,
|
||||
pub root: Option<GuestMetricNode>,
|
||||
pub zone: Zone,
|
||||
pub root: Option<ZoneMetricNode>,
|
||||
}
|
||||
|
||||
pub struct MultiMetricState {
|
||||
pub guests: Vec<MetricState>,
|
||||
pub zones: Vec<MetricState>,
|
||||
}
|
||||
|
||||
pub struct MultiMetricCollector {
|
||||
@ -72,26 +72,26 @@ impl MultiMetricCollector {
|
||||
|
||||
pub async fn process(&mut self, sender: Sender<MultiMetricState>) -> Result<()> {
|
||||
let mut events = self.events.subscribe();
|
||||
let mut guests: Vec<Guest> = self
|
||||
let mut zones: Vec<Zone> = self
|
||||
.client
|
||||
.list_guests(ListGuestsRequest {})
|
||||
.list_zones(ListZonesRequest {})
|
||||
.await?
|
||||
.into_inner()
|
||||
.guests;
|
||||
.zones;
|
||||
loop {
|
||||
let collect = select! {
|
||||
x = events.recv() => match x {
|
||||
Ok(event) => {
|
||||
let Event::GuestChanged(changed) = event;
|
||||
let Some(guest) = changed.guest else {
|
||||
let Event::ZoneChanged(changed) = event;
|
||||
let Some(zone) = changed.zone else {
|
||||
continue;
|
||||
};
|
||||
let Some(ref state) = guest.state else {
|
||||
let Some(ref state) = zone.state else {
|
||||
continue;
|
||||
};
|
||||
guests.retain(|x| x.id != guest.id);
|
||||
if state.status() != GuestStatus::Destroying {
|
||||
guests.push(guest);
|
||||
zones.retain(|x| x.id != zone.id);
|
||||
if state.status() != ZoneStatus::Destroying {
|
||||
zones.push(zone);
|
||||
}
|
||||
false
|
||||
},
|
||||
@ -111,19 +111,19 @@ impl MultiMetricCollector {
|
||||
}
|
||||
|
||||
let mut metrics = Vec::new();
|
||||
for guest in &guests {
|
||||
let Some(ref state) = guest.state else {
|
||||
for zone in &zones {
|
||||
let Some(ref state) = zone.state else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if state.status() != GuestStatus::Started {
|
||||
if state.status() != ZoneStatus::Started {
|
||||
continue;
|
||||
}
|
||||
|
||||
let root = timeout(
|
||||
Duration::from_secs(5),
|
||||
self.client.read_guest_metrics(ReadGuestMetricsRequest {
|
||||
guest_id: guest.id.clone(),
|
||||
self.client.read_zone_metrics(ReadZoneMetricsRequest {
|
||||
zone_id: zone.id.clone(),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@ -132,16 +132,16 @@ impl MultiMetricCollector {
|
||||
.map(|x| x.into_inner())
|
||||
.and_then(|x| x.root);
|
||||
metrics.push(MetricState {
|
||||
guest: guest.clone(),
|
||||
zone: zone.clone(),
|
||||
root,
|
||||
});
|
||||
}
|
||||
sender.send(MultiMetricState { guests: metrics }).await?;
|
||||
sender.send(MultiMetricState { zones: metrics }).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lookup<'a>(node: &'a GuestMetricNode, path: &str) -> Option<&'a GuestMetricNode> {
|
||||
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);
|
||||
};
|
||||
@ -149,7 +149,7 @@ pub fn lookup<'a>(node: &'a GuestMetricNode, path: &str) -> Option<&'a GuestMetr
|
||||
return lookup(next, b);
|
||||
}
|
||||
|
||||
pub fn lookup_metric_value(node: &GuestMetricNode, path: &str) -> Option<String> {
|
||||
pub fn lookup_metric_value(node: &ZoneMetricNode, path: &str) -> Option<String> {
|
||||
lookup(node, path).and_then(|x| {
|
||||
x.value
|
||||
.as_ref()
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "krata-daemon"
|
||||
description = "Daemon for the krata hypervisor."
|
||||
description = "Daemon for the krata isolation engine"
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
homepage.workspace = true
|
||||
@ -17,16 +17,18 @@ circular-buffer = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
env_logger = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
krata = { path = "../krata", version = "^0.0.10" }
|
||||
krata-oci = { path = "../oci", version = "^0.0.10" }
|
||||
krata-runtime = { path = "../runtime", version = "^0.0.10" }
|
||||
krata = { path = "../krata", version = "^0.0.15" }
|
||||
krata-oci = { path = "../oci", version = "^0.0.15" }
|
||||
krata-runtime = { path = "../runtime", version = "^0.0.15" }
|
||||
log = { workspace = true }
|
||||
prost = { workspace = true }
|
||||
redb = { workspace = true }
|
||||
scopeguard = { workspace = true }
|
||||
serde = { 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 }
|
||||
|
@ -1,15 +1,31 @@
|
||||
use std::{
|
||||
net::{SocketAddr, TcpStream},
|
||||
str::FromStr,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use env_logger::Env;
|
||||
use kratad::command::DaemonCommand;
|
||||
use env_logger::fmt::Target;
|
||||
use log::LevelFilter;
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
|
||||
use kratad::command::DaemonCommand;
|
||||
|
||||
#[tokio::main(flavor = "multi_thread", worker_threads = 10)]
|
||||
async fn main() -> Result<()> {
|
||||
env_logger::Builder::from_env(Env::default().default_filter_or("info"))
|
||||
.filter(Some("backhand::filesystem::writer"), LevelFilter::Warn)
|
||||
.init();
|
||||
let mut builder = env_logger::Builder::new();
|
||||
builder
|
||||
.filter_level(LevelFilter::Trace)
|
||||
.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();
|
||||
|
@ -6,7 +6,7 @@ use std::str::FromStr;
|
||||
use crate::Daemon;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version, about = "Krata hypervisor daemon")]
|
||||
#[command(version, about = "krata isolation engine daemon")]
|
||||
pub struct DaemonCommand {
|
||||
#[arg(
|
||||
short,
|
||||
|
63
crates/daemon/src/config.rs
Normal file
63
crates/daemon/src/config.rs
Normal file
@ -0,0 +1,63 @@
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||
pub struct OciConfig {
|
||||
#[serde(default)]
|
||||
pub seed: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||
pub struct DaemonPciConfig {
|
||||
#[serde(default)]
|
||||
pub devices: HashMap<String, DaemonPciDeviceConfig>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct DaemonPciDeviceConfig {
|
||||
pub locations: Vec<String>,
|
||||
#[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,
|
||||
}
|
||||
|
||||
impl DaemonConfig {
|
||||
pub async fn load(path: &Path) -> Result<DaemonConfig> {
|
||||
if path.exists() {
|
||||
let content = fs::read_to_string(path).await?;
|
||||
let config: DaemonConfig = toml::from_str(&content)?;
|
||||
Ok(config)
|
||||
} else {
|
||||
fs::write(&path, "").await?;
|
||||
Ok(DaemonConfig::default())
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ use tokio::{
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::glt::GuestLookupTable;
|
||||
use crate::zlt::ZoneLookupTable;
|
||||
|
||||
const CONSOLE_BUFFER_SIZE: usize = 1024 * 1024;
|
||||
type RawConsoleBuffer = CircularBuffer<CONSOLE_BUFFER_SIZE, u8>;
|
||||
@ -24,7 +24,7 @@ type BufferMap = Arc<Mutex<HashMap<u32, ConsoleBuffer>>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DaemonConsoleHandle {
|
||||
glt: GuestLookupTable,
|
||||
glt: ZoneLookupTable,
|
||||
listeners: ListenerMap,
|
||||
buffers: BufferMap,
|
||||
sender: Sender<(u32, Vec<u8>)>,
|
||||
@ -84,7 +84,7 @@ impl Drop for DaemonConsoleHandle {
|
||||
}
|
||||
|
||||
pub struct DaemonConsole {
|
||||
glt: GuestLookupTable,
|
||||
glt: ZoneLookupTable,
|
||||
listeners: ListenerMap,
|
||||
buffers: BufferMap,
|
||||
receiver: Receiver<(u32, Option<Vec<u8>>)>,
|
||||
@ -93,7 +93,7 @@ pub struct DaemonConsole {
|
||||
}
|
||||
|
||||
impl DaemonConsole {
|
||||
pub async fn new(glt: GuestLookupTable) -> Result<DaemonConsole> {
|
||||
pub async fn new(glt: ZoneLookupTable) -> Result<DaemonConsole> {
|
||||
let (service, sender, receiver) =
|
||||
ChannelService::new("krata-console".to_string(), Some(0)).await?;
|
||||
let task = service.launch().await?;
|
||||
|
@ -7,14 +7,16 @@ use krata::{
|
||||
ExecStreamRequestStdin, ExecStreamRequestUpdate, MetricsRequest, Request as IdmRequest,
|
||||
},
|
||||
v1::{
|
||||
common::{Guest, GuestState, GuestStatus, OciImageFormat},
|
||||
common::{OciImageFormat, Zone, ZoneState, ZoneStatus},
|
||||
control::{
|
||||
control_service_server::ControlService, ConsoleDataReply, ConsoleDataRequest,
|
||||
CreateGuestReply, CreateGuestRequest, DestroyGuestReply, DestroyGuestRequest,
|
||||
ExecGuestReply, ExecGuestRequest, IdentifyHostReply, IdentifyHostRequest,
|
||||
ListGuestsReply, ListGuestsRequest, PullImageReply, PullImageRequest,
|
||||
ReadGuestMetricsReply, ReadGuestMetricsRequest, ResolveGuestReply, ResolveGuestRequest,
|
||||
SnoopIdmReply, SnoopIdmRequest, WatchEventsReply, WatchEventsRequest,
|
||||
control_service_server::ControlService, CreateZoneReply, CreateZoneRequest,
|
||||
DestroyZoneReply, DestroyZoneRequest, DeviceInfo, ExecZoneReply, ExecZoneRequest,
|
||||
HostCpuTopologyInfo, HostCpuTopologyReply, HostCpuTopologyRequest,
|
||||
HostPowerManagementPolicy, IdentifyHostReply, IdentifyHostRequest, ListDevicesReply,
|
||||
ListDevicesRequest, ListZonesReply, ListZonesRequest, PullImageReply, PullImageRequest,
|
||||
ReadZoneMetricsReply, ReadZoneMetricsRequest, ResolveZoneReply, ResolveZoneRequest,
|
||||
SnoopIdmReply, SnoopIdmRequest, WatchEventsReply, WatchEventsRequest, ZoneConsoleReply,
|
||||
ZoneConsoleRequest,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -23,6 +25,7 @@ use krataoci::{
|
||||
packer::{service::OciPackerService, OciPackedFormat, OciPackedImage},
|
||||
progress::{OciProgress, OciProgressContext},
|
||||
};
|
||||
use kratart::Runtime;
|
||||
use std::{pin::Pin, str::FromStr};
|
||||
use tokio::{
|
||||
select,
|
||||
@ -34,9 +37,9 @@ use tonic::{Request, Response, Status, Streaming};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
command::DaemonCommand, console::DaemonConsoleHandle, db::GuestStore,
|
||||
event::DaemonEventContext, glt::GuestLookupTable, idm::DaemonIdmHandle,
|
||||
metrics::idm_metric_to_api, oci::convert_oci_progress,
|
||||
command::DaemonCommand, console::DaemonConsoleHandle, db::ZoneStore,
|
||||
devices::DaemonDeviceManager, event::DaemonEventContext, idm::DaemonIdmHandle,
|
||||
metrics::idm_metric_to_api, oci::convert_oci_progress, zlt::ZoneLookupTable,
|
||||
};
|
||||
|
||||
pub struct ApiError {
|
||||
@ -59,40 +62,47 @@ impl From<ApiError> for Status {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DaemonControlService {
|
||||
glt: GuestLookupTable,
|
||||
glt: ZoneLookupTable,
|
||||
devices: DaemonDeviceManager,
|
||||
events: DaemonEventContext,
|
||||
console: DaemonConsoleHandle,
|
||||
idm: DaemonIdmHandle,
|
||||
guests: GuestStore,
|
||||
guest_reconciler_notify: Sender<Uuid>,
|
||||
zones: ZoneStore,
|
||||
zone_reconciler_notify: Sender<Uuid>,
|
||||
packer: OciPackerService,
|
||||
runtime: Runtime,
|
||||
}
|
||||
|
||||
impl DaemonControlService {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
glt: GuestLookupTable,
|
||||
glt: ZoneLookupTable,
|
||||
devices: DaemonDeviceManager,
|
||||
events: DaemonEventContext,
|
||||
console: DaemonConsoleHandle,
|
||||
idm: DaemonIdmHandle,
|
||||
guests: GuestStore,
|
||||
guest_reconciler_notify: Sender<Uuid>,
|
||||
zones: ZoneStore,
|
||||
zone_reconciler_notify: Sender<Uuid>,
|
||||
packer: OciPackerService,
|
||||
runtime: Runtime,
|
||||
) -> Self {
|
||||
Self {
|
||||
glt,
|
||||
devices,
|
||||
events,
|
||||
console,
|
||||
idm,
|
||||
guests,
|
||||
guest_reconciler_notify,
|
||||
zones,
|
||||
zone_reconciler_notify,
|
||||
packer,
|
||||
runtime,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ConsoleDataSelect {
|
||||
Read(Option<Vec<u8>>),
|
||||
Write(Option<Result<ConsoleDataRequest, tonic::Status>>),
|
||||
Write(Option<Result<ZoneConsoleRequest, Status>>),
|
||||
}
|
||||
|
||||
enum PullImageSelect {
|
||||
@ -102,11 +112,11 @@ enum PullImageSelect {
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl ControlService for DaemonControlService {
|
||||
type ExecGuestStream =
|
||||
Pin<Box<dyn Stream<Item = Result<ExecGuestReply, Status>> + Send + 'static>>;
|
||||
type ExecZoneStream =
|
||||
Pin<Box<dyn Stream<Item = Result<ExecZoneReply, Status>> + Send + 'static>>;
|
||||
|
||||
type ConsoleDataStream =
|
||||
Pin<Box<dyn Stream<Item = Result<ConsoleDataReply, Status>> + Send + 'static>>;
|
||||
type AttachZoneConsoleStream =
|
||||
Pin<Box<dyn Stream<Item = Result<ZoneConsoleReply, Status>> + Send + 'static>>;
|
||||
|
||||
type PullImageStream =
|
||||
Pin<Box<dyn Stream<Item = Result<PullImageReply, Status>> + Send + 'static>>;
|
||||
@ -129,25 +139,25 @@ impl ControlService for DaemonControlService {
|
||||
}))
|
||||
}
|
||||
|
||||
async fn create_guest(
|
||||
async fn create_zone(
|
||||
&self,
|
||||
request: Request<CreateGuestRequest>,
|
||||
) -> Result<Response<CreateGuestReply>, Status> {
|
||||
request: Request<CreateZoneRequest>,
|
||||
) -> Result<Response<CreateZoneReply>, Status> {
|
||||
let request = request.into_inner();
|
||||
let Some(spec) = request.spec else {
|
||||
return Err(ApiError {
|
||||
message: "guest spec not provided".to_string(),
|
||||
message: "zone spec not provided".to_string(),
|
||||
}
|
||||
.into());
|
||||
};
|
||||
let uuid = Uuid::new_v4();
|
||||
self.guests
|
||||
self.zones
|
||||
.update(
|
||||
uuid,
|
||||
Guest {
|
||||
Zone {
|
||||
id: uuid.to_string(),
|
||||
state: Some(GuestState {
|
||||
status: GuestStatus::Starting.into(),
|
||||
state: Some(ZoneState {
|
||||
status: ZoneStatus::Starting.into(),
|
||||
network: None,
|
||||
exit_info: None,
|
||||
error_info: None,
|
||||
@ -159,21 +169,21 @@ impl ControlService for DaemonControlService {
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
self.guest_reconciler_notify
|
||||
self.zone_reconciler_notify
|
||||
.send(uuid)
|
||||
.await
|
||||
.map_err(|x| ApiError {
|
||||
message: x.to_string(),
|
||||
})?;
|
||||
Ok(Response::new(CreateGuestReply {
|
||||
guest_id: uuid.to_string(),
|
||||
Ok(Response::new(CreateZoneReply {
|
||||
zone_id: uuid.to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn exec_guest(
|
||||
async fn exec_zone(
|
||||
&self,
|
||||
request: Request<Streaming<ExecGuestRequest>>,
|
||||
) -> Result<Response<Self::ExecGuestStream>, Status> {
|
||||
request: Request<Streaming<ExecZoneRequest>>,
|
||||
) -> Result<Response<Self::ExecZoneStream>, Status> {
|
||||
let mut input = request.into_inner();
|
||||
let Some(request) = input.next().await else {
|
||||
return Err(ApiError {
|
||||
@ -190,7 +200,7 @@ impl ControlService for DaemonControlService {
|
||||
.into());
|
||||
};
|
||||
|
||||
let uuid = Uuid::from_str(&request.guest_id).map_err(|error| ApiError {
|
||||
let uuid = Uuid::from_str(&request.zone_id).map_err(|error| ApiError {
|
||||
message: error.to_string(),
|
||||
})?;
|
||||
let idm = self.idm.client(uuid).await.map_err(|error| ApiError {
|
||||
@ -222,7 +232,7 @@ impl ControlService for DaemonControlService {
|
||||
loop {
|
||||
select! {
|
||||
x = input.next() => if let Some(update) = x {
|
||||
let update: Result<ExecGuestRequest, Status> = update.map_err(|error| ApiError {
|
||||
let update: Result<ExecZoneRequest, Status> = update.map_err(|error| ApiError {
|
||||
message: error.to_string()
|
||||
}.into());
|
||||
|
||||
@ -242,7 +252,7 @@ impl ControlService for DaemonControlService {
|
||||
let Some(IdmResponseType::ExecStream(update)) = response.response else {
|
||||
break;
|
||||
};
|
||||
let reply = ExecGuestReply {
|
||||
let reply = ExecZoneReply {
|
||||
exited: update.exited,
|
||||
error: update.error,
|
||||
exit_code: update.exit_code,
|
||||
@ -259,80 +269,80 @@ impl ControlService for DaemonControlService {
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Response::new(Box::pin(output) as Self::ExecGuestStream))
|
||||
Ok(Response::new(Box::pin(output) as Self::ExecZoneStream))
|
||||
}
|
||||
|
||||
async fn destroy_guest(
|
||||
async fn destroy_zone(
|
||||
&self,
|
||||
request: Request<DestroyGuestRequest>,
|
||||
) -> Result<Response<DestroyGuestReply>, Status> {
|
||||
request: Request<DestroyZoneRequest>,
|
||||
) -> Result<Response<DestroyZoneReply>, Status> {
|
||||
let request = request.into_inner();
|
||||
let uuid = Uuid::from_str(&request.guest_id).map_err(|error| ApiError {
|
||||
let uuid = Uuid::from_str(&request.zone_id).map_err(|error| ApiError {
|
||||
message: error.to_string(),
|
||||
})?;
|
||||
let Some(mut guest) = self.guests.read(uuid).await.map_err(ApiError::from)? else {
|
||||
let Some(mut zone) = self.zones.read(uuid).await.map_err(ApiError::from)? else {
|
||||
return Err(ApiError {
|
||||
message: "guest not found".to_string(),
|
||||
message: "zone not found".to_string(),
|
||||
}
|
||||
.into());
|
||||
};
|
||||
|
||||
guest.state = Some(guest.state.as_mut().cloned().unwrap_or_default());
|
||||
zone.state = Some(zone.state.as_mut().cloned().unwrap_or_default());
|
||||
|
||||
if guest.state.as_ref().unwrap().status() == GuestStatus::Destroyed {
|
||||
if zone.state.as_ref().unwrap().status() == ZoneStatus::Destroyed {
|
||||
return Err(ApiError {
|
||||
message: "guest already destroyed".to_string(),
|
||||
message: "zone already destroyed".to_string(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
guest.state.as_mut().unwrap().status = GuestStatus::Destroying.into();
|
||||
self.guests
|
||||
.update(uuid, guest)
|
||||
zone.state.as_mut().unwrap().status = ZoneStatus::Destroying.into();
|
||||
self.zones
|
||||
.update(uuid, zone)
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
self.guest_reconciler_notify
|
||||
self.zone_reconciler_notify
|
||||
.send(uuid)
|
||||
.await
|
||||
.map_err(|x| ApiError {
|
||||
message: x.to_string(),
|
||||
})?;
|
||||
Ok(Response::new(DestroyGuestReply {}))
|
||||
Ok(Response::new(DestroyZoneReply {}))
|
||||
}
|
||||
|
||||
async fn list_guests(
|
||||
async fn list_zones(
|
||||
&self,
|
||||
request: Request<ListGuestsRequest>,
|
||||
) -> Result<Response<ListGuestsReply>, Status> {
|
||||
request: Request<ListZonesRequest>,
|
||||
) -> Result<Response<ListZonesReply>, Status> {
|
||||
let _ = request.into_inner();
|
||||
let guests = self.guests.list().await.map_err(ApiError::from)?;
|
||||
let guests = guests.into_values().collect::<Vec<Guest>>();
|
||||
Ok(Response::new(ListGuestsReply { guests }))
|
||||
let zones = self.zones.list().await.map_err(ApiError::from)?;
|
||||
let zones = zones.into_values().collect::<Vec<Zone>>();
|
||||
Ok(Response::new(ListZonesReply { zones }))
|
||||
}
|
||||
|
||||
async fn resolve_guest(
|
||||
async fn resolve_zone(
|
||||
&self,
|
||||
request: Request<ResolveGuestRequest>,
|
||||
) -> Result<Response<ResolveGuestReply>, Status> {
|
||||
request: Request<ResolveZoneRequest>,
|
||||
) -> Result<Response<ResolveZoneReply>, Status> {
|
||||
let request = request.into_inner();
|
||||
let guests = self.guests.list().await.map_err(ApiError::from)?;
|
||||
let guests = guests
|
||||
let zones = self.zones.list().await.map_err(ApiError::from)?;
|
||||
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::<Vec<Guest>>();
|
||||
Ok(Response::new(ResolveGuestReply {
|
||||
guest: guests.first().cloned(),
|
||||
.collect::<Vec<Zone>>();
|
||||
Ok(Response::new(ResolveZoneReply {
|
||||
zone: zones.first().cloned(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn console_data(
|
||||
async fn attach_zone_console(
|
||||
&self,
|
||||
request: Request<Streaming<ConsoleDataRequest>>,
|
||||
) -> Result<Response<Self::ConsoleDataStream>, Status> {
|
||||
request: Request<Streaming<ZoneConsoleRequest>>,
|
||||
) -> Result<Response<Self::AttachZoneConsoleStream>, Status> {
|
||||
let mut input = request.into_inner();
|
||||
let Some(request) = input.next().await else {
|
||||
return Err(ApiError {
|
||||
@ -341,7 +351,7 @@ impl ControlService for DaemonControlService {
|
||||
.into());
|
||||
};
|
||||
let request = request?;
|
||||
let uuid = Uuid::from_str(&request.guest_id).map_err(|error| ApiError {
|
||||
let uuid = Uuid::from_str(&request.zone_id).map_err(|error| ApiError {
|
||||
message: error.to_string(),
|
||||
})?;
|
||||
let (sender, mut receiver) = channel(100);
|
||||
@ -354,7 +364,7 @@ impl ControlService for DaemonControlService {
|
||||
})?;
|
||||
|
||||
let output = try_stream! {
|
||||
yield ConsoleDataReply { data: console.initial.clone(), };
|
||||
yield ZoneConsoleReply { data: console.initial.clone(), };
|
||||
loop {
|
||||
let what = select! {
|
||||
x = receiver.recv() => ConsoleDataSelect::Read(x),
|
||||
@ -363,7 +373,7 @@ impl ControlService for DaemonControlService {
|
||||
|
||||
match what {
|
||||
ConsoleDataSelect::Read(Some(data)) => {
|
||||
yield ConsoleDataReply { data, };
|
||||
yield ZoneConsoleReply { data, };
|
||||
},
|
||||
|
||||
ConsoleDataSelect::Read(None) => {
|
||||
@ -386,15 +396,17 @@ impl ControlService for DaemonControlService {
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Response::new(Box::pin(output) as Self::ConsoleDataStream))
|
||||
Ok(Response::new(
|
||||
Box::pin(output) as Self::AttachZoneConsoleStream
|
||||
))
|
||||
}
|
||||
|
||||
async fn read_guest_metrics(
|
||||
async fn read_zone_metrics(
|
||||
&self,
|
||||
request: Request<ReadGuestMetricsRequest>,
|
||||
) -> Result<Response<ReadGuestMetricsReply>, Status> {
|
||||
request: Request<ReadZoneMetricsRequest>,
|
||||
) -> Result<Response<ReadZoneMetricsReply>, Status> {
|
||||
let request = request.into_inner();
|
||||
let uuid = Uuid::from_str(&request.guest_id).map_err(|error| ApiError {
|
||||
let uuid = Uuid::from_str(&request.zone_id).map_err(|error| ApiError {
|
||||
message: error.to_string(),
|
||||
})?;
|
||||
let client = self.idm.client(uuid).await.map_err(|error| ApiError {
|
||||
@ -410,7 +422,7 @@ impl ControlService for DaemonControlService {
|
||||
message: error.to_string(),
|
||||
})?;
|
||||
|
||||
let mut reply = ReadGuestMetricsReply::default();
|
||||
let mut reply = ReadZoneMetricsReply::default();
|
||||
if let Some(IdmResponseType::Metrics(metrics)) = response.response {
|
||||
reply.root = metrics.root.map(idm_metric_to_api);
|
||||
}
|
||||
@ -436,7 +448,7 @@ impl ControlService for DaemonControlService {
|
||||
|
||||
let output = try_stream! {
|
||||
let mut task = tokio::task::spawn(async move {
|
||||
our_packer.request(name, format, request.overwrite_cache, context).await
|
||||
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| {
|
||||
@ -524,4 +536,76 @@ impl ControlService for DaemonControlService {
|
||||
};
|
||||
Ok(Response::new(Box::pin(output) as Self::SnoopIdmStream))
|
||||
}
|
||||
|
||||
async fn list_devices(
|
||||
&self,
|
||||
request: Request<ListDevicesRequest>,
|
||||
) -> Result<Response<ListDevicesReply>, Status> {
|
||||
let _ = request.into_inner();
|
||||
let mut devices = Vec::new();
|
||||
let state = self.devices.copy().await.map_err(|error| ApiError {
|
||||
message: error.to_string(),
|
||||
})?;
|
||||
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(Response::new(ListDevicesReply { devices }))
|
||||
}
|
||||
|
||||
async fn get_host_cpu_topology(
|
||||
&self,
|
||||
request: Request<HostCpuTopologyRequest>,
|
||||
) -> Result<Response<HostCpuTopologyReply>, Status> {
|
||||
let _ = request.into_inner();
|
||||
let power = self
|
||||
.runtime
|
||||
.power_management_context()
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
let cputopo = power.cpu_topology().await.map_err(ApiError::from)?;
|
||||
let mut cpus = vec![];
|
||||
|
||||
for cpu in cputopo {
|
||||
cpus.push(HostCpuTopologyInfo {
|
||||
core: cpu.core,
|
||||
socket: cpu.socket,
|
||||
node: cpu.node,
|
||||
thread: cpu.thread,
|
||||
class: cpu.class as i32,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(Response::new(HostCpuTopologyReply { cpus }))
|
||||
}
|
||||
|
||||
async fn set_host_power_management_policy(
|
||||
&self,
|
||||
request: Request<HostPowerManagementPolicy>,
|
||||
) -> Result<Response<HostPowerManagementPolicy>, Status> {
|
||||
let policy = request.into_inner();
|
||||
let power = self
|
||||
.runtime
|
||||
.power_management_context()
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
let scheduler = &policy.scheduler;
|
||||
|
||||
power
|
||||
.set_smt_policy(policy.smt_awareness)
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
power
|
||||
.set_scheduler_policy(scheduler)
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
Ok(Response::new(HostPowerManagementPolicy {
|
||||
scheduler: scheduler.to_string(),
|
||||
smt_awareness: policy.smt_awareness,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -1,66 +1,66 @@
|
||||
use std::{collections::HashMap, path::Path, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use krata::v1::common::Guest;
|
||||
use krata::v1::common::Zone;
|
||||
use log::error;
|
||||
use prost::Message;
|
||||
use redb::{Database, ReadableTable, TableDefinition};
|
||||
use uuid::Uuid;
|
||||
|
||||
const GUESTS: TableDefinition<u128, &[u8]> = TableDefinition::new("guests");
|
||||
const ZONES: TableDefinition<u128, &[u8]> = TableDefinition::new("zones");
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GuestStore {
|
||||
pub struct ZoneStore {
|
||||
database: Arc<Database>,
|
||||
}
|
||||
|
||||
impl GuestStore {
|
||||
impl ZoneStore {
|
||||
pub fn open(path: &Path) -> Result<Self> {
|
||||
let database = Database::create(path)?;
|
||||
let write = database.begin_write()?;
|
||||
let _ = write.open_table(GUESTS);
|
||||
let _ = write.open_table(ZONES);
|
||||
write.commit()?;
|
||||
Ok(GuestStore {
|
||||
Ok(ZoneStore {
|
||||
database: Arc::new(database),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn read(&self, id: Uuid) -> Result<Option<Guest>> {
|
||||
pub async fn read(&self, id: Uuid) -> Result<Option<Zone>> {
|
||||
let read = self.database.begin_read()?;
|
||||
let table = read.open_table(GUESTS)?;
|
||||
let table = read.open_table(ZONES)?;
|
||||
let Some(entry) = table.get(id.to_u128_le())? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let bytes = entry.value();
|
||||
Ok(Some(Guest::decode(bytes)?))
|
||||
Ok(Some(Zone::decode(bytes)?))
|
||||
}
|
||||
|
||||
pub async fn list(&self) -> Result<HashMap<Uuid, Guest>> {
|
||||
let mut guests: HashMap<Uuid, Guest> = HashMap::new();
|
||||
pub async fn list(&self) -> Result<HashMap<Uuid, Zone>> {
|
||||
let mut zones: HashMap<Uuid, Zone> = HashMap::new();
|
||||
let read = self.database.begin_read()?;
|
||||
let table = read.open_table(GUESTS)?;
|
||||
let table = read.open_table(ZONES)?;
|
||||
for result in table.iter()? {
|
||||
let (key, value) = result?;
|
||||
let uuid = Uuid::from_u128_le(key.value());
|
||||
let state = match Guest::decode(value.value()) {
|
||||
let state = match Zone::decode(value.value()) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
error!(
|
||||
"found invalid guest state in database for uuid {}: {}",
|
||||
"found invalid zone state in database for uuid {}: {}",
|
||||
uuid, error
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
guests.insert(uuid, state);
|
||||
zones.insert(uuid, state);
|
||||
}
|
||||
Ok(guests)
|
||||
Ok(zones)
|
||||
}
|
||||
|
||||
pub async fn update(&self, id: Uuid, entry: Guest) -> Result<()> {
|
||||
pub async fn update(&self, id: Uuid, entry: Zone) -> Result<()> {
|
||||
let write = self.database.begin_write()?;
|
||||
{
|
||||
let mut table = write.open_table(GUESTS)?;
|
||||
let mut table = write.open_table(ZONES)?;
|
||||
let bytes = entry.encode_to_vec();
|
||||
table.insert(id.to_u128_le(), bytes.as_slice())?;
|
||||
}
|
||||
@ -71,7 +71,7 @@ impl GuestStore {
|
||||
pub async fn remove(&self, id: Uuid) -> Result<()> {
|
||||
let write = self.database.begin_write()?;
|
||||
{
|
||||
let mut table = write.open_table(GUESTS)?;
|
||||
let mut table = write.open_table(ZONES)?;
|
||||
table.remove(id.to_u128_le())?;
|
||||
}
|
||||
write.commit()?;
|
||||
|
106
crates/daemon/src/devices.rs
Normal file
106
crates/daemon/src/devices.rs
Normal file
@ -0,0 +1,106 @@
|
||||
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<DaemonPciDeviceConfig>,
|
||||
pub owner: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DaemonDeviceManager {
|
||||
config: Arc<DaemonConfig>,
|
||||
devices: Arc<RwLock<HashMap<String, DaemonDeviceState>>>,
|
||||
}
|
||||
|
||||
impl DaemonDeviceManager {
|
||||
pub fn new(config: Arc<DaemonConfig>) -> Self {
|
||||
Self {
|
||||
config,
|
||||
devices: Arc::new(RwLock::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn claim(&self, device: &str, uuid: Uuid) -> Result<DaemonDeviceState> {
|
||||
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<String, Uuid>) -> 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<HashMap<String, DaemonDeviceState>> {
|
||||
let devices = self.devices.read().await;
|
||||
Ok(devices.clone())
|
||||
}
|
||||
}
|
@ -4,10 +4,12 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::{db::ZoneStore, idm::DaemonIdmHandle};
|
||||
use anyhow::Result;
|
||||
use krata::v1::common::ZoneExitInfo;
|
||||
use krata::{
|
||||
idm::{internal::event::Event as EventType, internal::Event},
|
||||
v1::common::{GuestExitInfo, GuestState, GuestStatus},
|
||||
v1::common::{ZoneState, ZoneStatus},
|
||||
};
|
||||
use log::{error, warn};
|
||||
use tokio::{
|
||||
@ -21,8 +23,6 @@ use tokio::{
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{db::GuestStore, idm::DaemonIdmHandle};
|
||||
|
||||
pub type DaemonEvent = krata::v1::control::watch_events_reply::Event;
|
||||
|
||||
const EVENT_CHANNEL_QUEUE_LEN: usize = 1000;
|
||||
@ -45,8 +45,8 @@ impl DaemonEventContext {
|
||||
}
|
||||
|
||||
pub struct DaemonEventGenerator {
|
||||
guests: GuestStore,
|
||||
guest_reconciler_notify: Sender<Uuid>,
|
||||
zones: ZoneStore,
|
||||
zone_reconciler_notify: Sender<Uuid>,
|
||||
feed: broadcast::Receiver<DaemonEvent>,
|
||||
idm: DaemonIdmHandle,
|
||||
idms: HashMap<u32, (Uuid, JoinHandle<()>)>,
|
||||
@ -57,15 +57,15 @@ pub struct DaemonEventGenerator {
|
||||
|
||||
impl DaemonEventGenerator {
|
||||
pub async fn new(
|
||||
guests: GuestStore,
|
||||
guest_reconciler_notify: Sender<Uuid>,
|
||||
zones: ZoneStore,
|
||||
zone_reconciler_notify: Sender<Uuid>,
|
||||
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 {
|
||||
guests,
|
||||
guest_reconciler_notify,
|
||||
zones,
|
||||
zone_reconciler_notify,
|
||||
feed: sender.subscribe(),
|
||||
idm,
|
||||
idms: HashMap::new(),
|
||||
@ -78,20 +78,20 @@ impl DaemonEventGenerator {
|
||||
}
|
||||
|
||||
async fn handle_feed_event(&mut self, event: &DaemonEvent) -> Result<()> {
|
||||
let DaemonEvent::GuestChanged(changed) = event;
|
||||
let Some(ref guest) = changed.guest else {
|
||||
let DaemonEvent::ZoneChanged(changed) = event;
|
||||
let Some(ref zone) = changed.zone else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let Some(ref state) = guest.state else {
|
||||
let Some(ref state) = zone.state else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let status = state.status();
|
||||
let id = Uuid::from_str(&guest.id)?;
|
||||
let id = Uuid::from_str(&zone.id)?;
|
||||
let domid = state.domid;
|
||||
match status {
|
||||
GuestStatus::Started => {
|
||||
ZoneStatus::Started => {
|
||||
if let Entry::Vacant(e) = self.idms.entry(domid) {
|
||||
let client = self.idm.client_by_domid(domid).await?;
|
||||
let mut receiver = client.subscribe().await?;
|
||||
@ -111,7 +111,7 @@ impl DaemonEventGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
GuestStatus::Destroyed => {
|
||||
ZoneStatus::Destroyed => {
|
||||
if let Some((_, handle)) = self.idms.remove(&domid) {
|
||||
handle.abort();
|
||||
}
|
||||
@ -130,18 +130,18 @@ impl DaemonEventGenerator {
|
||||
}
|
||||
|
||||
async fn handle_exit_code(&mut self, id: Uuid, code: i32) -> Result<()> {
|
||||
if let Some(mut guest) = self.guests.read(id).await? {
|
||||
guest.state = Some(GuestState {
|
||||
status: GuestStatus::Exited.into(),
|
||||
network: guest.state.clone().unwrap_or_default().network,
|
||||
exit_info: Some(GuestExitInfo { code }),
|
||||
if let Some(mut zone) = self.zones.read(id).await? {
|
||||
zone.state = Some(ZoneState {
|
||||
status: ZoneStatus::Exited.into(),
|
||||
network: zone.state.clone().unwrap_or_default().network,
|
||||
exit_info: Some(ZoneExitInfo { code }),
|
||||
error_info: None,
|
||||
host: guest.state.clone().map(|x| x.host).unwrap_or_default(),
|
||||
domid: guest.state.clone().map(|x| x.domid).unwrap_or(u32::MAX),
|
||||
host: zone.state.clone().map(|x| x.host).unwrap_or_default(),
|
||||
domid: zone.state.clone().map(|x| x.domid).unwrap_or(u32::MAX),
|
||||
});
|
||||
|
||||
self.guests.update(id, guest).await?;
|
||||
self.guest_reconciler_notify.send(id).await?;
|
||||
self.zones.update(id, zone).await?;
|
||||
self.zone_reconciler_notify.send(id).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -24,14 +24,14 @@ use tokio::{
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::glt::GuestLookupTable;
|
||||
use crate::zlt::ZoneLookupTable;
|
||||
|
||||
type BackendFeedMap = Arc<Mutex<HashMap<u32, Sender<IdmTransportPacket>>>>;
|
||||
type ClientMap = Arc<Mutex<HashMap<u32, IdmInternalClient>>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DaemonIdmHandle {
|
||||
glt: GuestLookupTable,
|
||||
glt: ZoneLookupTable,
|
||||
clients: ClientMap,
|
||||
feeds: BackendFeedMap,
|
||||
tx_sender: Sender<(u32, IdmTransportPacket)>,
|
||||
@ -72,7 +72,7 @@ pub struct DaemonIdmSnoopPacket {
|
||||
}
|
||||
|
||||
pub struct DaemonIdm {
|
||||
glt: GuestLookupTable,
|
||||
glt: ZoneLookupTable,
|
||||
clients: ClientMap,
|
||||
feeds: BackendFeedMap,
|
||||
tx_sender: Sender<(u32, IdmTransportPacket)>,
|
||||
@ -84,7 +84,7 @@ pub struct DaemonIdm {
|
||||
}
|
||||
|
||||
impl DaemonIdm {
|
||||
pub async fn new(glt: GuestLookupTable) -> Result<DaemonIdm> {
|
||||
pub async fn new(glt: ZoneLookupTable) -> Result<DaemonIdm> {
|
||||
let (service, tx_raw_sender, rx_receiver) =
|
||||
ChannelService::new("krata-channel".to_string(), None).await?;
|
||||
let (tx_sender, tx_receiver) = channel(100);
|
||||
@ -136,34 +136,36 @@ impl DaemonIdm {
|
||||
if let Some(data) = data {
|
||||
let buffer = buffers.entry(domid).or_insert_with_key(|_| BytesMut::new());
|
||||
buffer.extend_from_slice(&data);
|
||||
if buffer.len() < 6 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if buffer[0] != 0xff || buffer[1] != 0xff {
|
||||
buffer.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
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 {
|
||||
continue;
|
||||
}
|
||||
let mut packet = buffer.split_to(needed);
|
||||
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 });
|
||||
loop {
|
||||
if buffer.len() < 6 {
|
||||
break;
|
||||
}
|
||||
|
||||
Err(packet) => {
|
||||
warn!("received invalid packet from domain {}: {}", domid, packet);
|
||||
if buffer[0] != 0xff || buffer[1] != 0xff {
|
||||
buffer.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
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 {
|
||||
break;
|
||||
}
|
||||
let mut packet = buffer.split_to(needed);
|
||||
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 {
|
||||
|
@ -1,17 +1,18 @@
|
||||
use std::{net::SocketAddr, path::PathBuf, str::FromStr};
|
||||
use std::{net::SocketAddr, path::PathBuf, str::FromStr, sync::Arc};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use config::DaemonConfig;
|
||||
use console::{DaemonConsole, DaemonConsoleHandle};
|
||||
use control::DaemonControlService;
|
||||
use db::GuestStore;
|
||||
use db::ZoneStore;
|
||||
use devices::DaemonDeviceManager;
|
||||
use event::{DaemonEventContext, DaemonEventGenerator};
|
||||
use glt::GuestLookupTable;
|
||||
use idm::{DaemonIdm, DaemonIdmHandle};
|
||||
use krata::{dial::ControlDialAddress, v1::control::control_service_server::ControlServiceServer};
|
||||
use krataoci::{packer::service::OciPackerService, registry::OciPlatform};
|
||||
use kratart::Runtime;
|
||||
use log::info;
|
||||
use reconcile::guest::GuestReconciler;
|
||||
use reconcile::zone::ZoneReconciler;
|
||||
use tokio::{
|
||||
fs,
|
||||
net::UnixListener,
|
||||
@ -21,41 +22,55 @@ use tokio::{
|
||||
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 glt;
|
||||
pub mod idm;
|
||||
pub mod metrics;
|
||||
pub mod oci;
|
||||
pub mod reconcile;
|
||||
pub mod zlt;
|
||||
|
||||
pub struct Daemon {
|
||||
store: String,
|
||||
glt: GuestLookupTable,
|
||||
guests: GuestStore,
|
||||
_config: Arc<DaemonConfig>,
|
||||
glt: ZoneLookupTable,
|
||||
devices: DaemonDeviceManager,
|
||||
zones: ZoneStore,
|
||||
events: DaemonEventContext,
|
||||
guest_reconciler_task: JoinHandle<()>,
|
||||
guest_reconciler_notify: Sender<Uuid>,
|
||||
zone_reconciler_task: JoinHandle<()>,
|
||||
zone_reconciler_notify: Sender<Uuid>,
|
||||
generator_task: JoinHandle<()>,
|
||||
idm: DaemonIdmHandle,
|
||||
console: DaemonConsoleHandle,
|
||||
packer: OciPackerService,
|
||||
runtime: Runtime,
|
||||
}
|
||||
|
||||
const GUEST_RECONCILER_QUEUE_LEN: usize = 1000;
|
||||
const ZONE_RECONCILER_QUEUE_LEN: usize = 1000;
|
||||
|
||||
impl Daemon {
|
||||
pub async fn new(store: String) -> Result<Self> {
|
||||
let mut image_cache_dir = PathBuf::from(store.clone());
|
||||
let store_dir = PathBuf::from(store.clone());
|
||||
let mut config_path = store_dir.clone();
|
||||
config_path.push("config.toml");
|
||||
|
||||
let config = DaemonConfig::load(&config_path).await?;
|
||||
let config = Arc::new(config);
|
||||
let devices = DaemonDeviceManager::new(config.clone());
|
||||
|
||||
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?;
|
||||
|
||||
let mut host_uuid_path = PathBuf::from(store.clone());
|
||||
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?;
|
||||
@ -74,61 +89,79 @@ impl Daemon {
|
||||
generated
|
||||
};
|
||||
|
||||
let initrd_path = detect_guest_file(&store, "initrd")?;
|
||||
let kernel_path = detect_guest_file(&store, "kernel")?;
|
||||
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")?;
|
||||
|
||||
let packer = OciPackerService::new(None, &image_cache_dir, OciPlatform::current()).await?;
|
||||
let runtime = Runtime::new().await?;
|
||||
let glt = GuestLookupTable::new(0, host_uuid);
|
||||
let guests_db_path = format!("{}/guests.db", store);
|
||||
let guests = GuestStore::open(&PathBuf::from(guests_db_path))?;
|
||||
let (guest_reconciler_notify, guest_reconciler_receiver) =
|
||||
channel::<Uuid>(GUEST_RECONCILER_QUEUE_LEN);
|
||||
let seed = config.oci.seed.clone().map(PathBuf::from);
|
||||
let packer = OciPackerService::new(seed, &image_cache_dir, OciPlatform::current()).await?;
|
||||
let runtime = Runtime::new(host_uuid).await?;
|
||||
let glt = ZoneLookupTable::new(0, host_uuid);
|
||||
let zones_db_path = format!("{}/zones.db", store);
|
||||
let zones = ZoneStore::open(&PathBuf::from(zones_db_path))?;
|
||||
let (zone_reconciler_notify, zone_reconciler_receiver) =
|
||||
channel::<Uuid>(ZONE_RECONCILER_QUEUE_LEN);
|
||||
let idm = DaemonIdm::new(glt.clone()).await?;
|
||||
let idm = idm.launch().await?;
|
||||
let console = DaemonConsole::new(glt.clone()).await?;
|
||||
let console = console.launch().await?;
|
||||
let (events, generator) =
|
||||
DaemonEventGenerator::new(guests.clone(), guest_reconciler_notify.clone(), idm.clone())
|
||||
DaemonEventGenerator::new(zones.clone(), zone_reconciler_notify.clone(), idm.clone())
|
||||
.await?;
|
||||
let runtime_for_reconciler = runtime.dupe().await?;
|
||||
let guest_reconciler = GuestReconciler::new(
|
||||
let zone_reconciler = ZoneReconciler::new(
|
||||
devices.clone(),
|
||||
glt.clone(),
|
||||
guests.clone(),
|
||||
zones.clone(),
|
||||
events.clone(),
|
||||
runtime_for_reconciler,
|
||||
packer.clone(),
|
||||
guest_reconciler_notify.clone(),
|
||||
zone_reconciler_notify.clone(),
|
||||
kernel_path,
|
||||
initrd_path,
|
||||
addons_path,
|
||||
)?;
|
||||
|
||||
let guest_reconciler_task = guest_reconciler.launch(guest_reconciler_receiver).await?;
|
||||
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.
|
||||
// FIXME: Power management hypercalls fail when running as an L1 hypervisor.
|
||||
// let power = runtime.power_management_context().await?;
|
||||
// power.set_smt_policy(true).await?;
|
||||
// power
|
||||
// .set_scheduler_policy("performance".to_string())
|
||||
// .await?;
|
||||
|
||||
Ok(Self {
|
||||
store,
|
||||
_config: config,
|
||||
glt,
|
||||
guests,
|
||||
devices,
|
||||
zones,
|
||||
events,
|
||||
guest_reconciler_task,
|
||||
guest_reconciler_notify,
|
||||
zone_reconciler_task,
|
||||
zone_reconciler_notify,
|
||||
generator_task,
|
||||
idm,
|
||||
console,
|
||||
packer,
|
||||
runtime,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn listen(&mut self, addr: ControlDialAddress) -> Result<()> {
|
||||
let control_service = DaemonControlService::new(
|
||||
self.glt.clone(),
|
||||
self.devices.clone(),
|
||||
self.events.clone(),
|
||||
self.console.clone(),
|
||||
self.idm.clone(),
|
||||
self.guests.clone(),
|
||||
self.guest_reconciler_notify.clone(),
|
||||
self.zones.clone(),
|
||||
self.zone_reconciler_notify.clone(),
|
||||
self.packer.clone(),
|
||||
self.runtime.clone(),
|
||||
);
|
||||
|
||||
let mut server = Server::builder();
|
||||
@ -181,20 +214,20 @@ impl Daemon {
|
||||
|
||||
impl Drop for Daemon {
|
||||
fn drop(&mut self) {
|
||||
self.guest_reconciler_task.abort();
|
||||
self.zone_reconciler_task.abort();
|
||||
self.generator_task.abort();
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_guest_file(store: &str, name: &str) -> Result<PathBuf> {
|
||||
let mut path = PathBuf::from(format!("{}/guest/{}", store, name));
|
||||
fn detect_zone_path(store: &str, name: &str) -> Result<PathBuf> {
|
||||
let mut path = PathBuf::from(format!("{}/zone/{}", store, name));
|
||||
if path.is_file() {
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
path = PathBuf::from(format!("/usr/share/krata/guest/{}", name));
|
||||
path = PathBuf::from(format!("/usr/share/krata/zone/{}", name));
|
||||
if path.is_file() {
|
||||
return Ok(path);
|
||||
}
|
||||
Err(anyhow!("unable to find required guest file: {}", name))
|
||||
Err(anyhow!("unable to find required zone file: {}", name))
|
||||
}
|
||||
|
@ -1,20 +1,20 @@
|
||||
use krata::{
|
||||
idm::internal::{MetricFormat, MetricNode},
|
||||
v1::common::{GuestMetricFormat, GuestMetricNode},
|
||||
v1::common::{ZoneMetricFormat, ZoneMetricNode},
|
||||
};
|
||||
|
||||
fn idm_metric_format_to_api(format: MetricFormat) -> GuestMetricFormat {
|
||||
fn idm_metric_format_to_api(format: MetricFormat) -> ZoneMetricFormat {
|
||||
match format {
|
||||
MetricFormat::Unknown => GuestMetricFormat::Unknown,
|
||||
MetricFormat::Bytes => GuestMetricFormat::Bytes,
|
||||
MetricFormat::Integer => GuestMetricFormat::Integer,
|
||||
MetricFormat::DurationSeconds => GuestMetricFormat::DurationSeconds,
|
||||
MetricFormat::Unknown => ZoneMetricFormat::Unknown,
|
||||
MetricFormat::Bytes => ZoneMetricFormat::Bytes,
|
||||
MetricFormat::Integer => ZoneMetricFormat::Integer,
|
||||
MetricFormat::DurationSeconds => ZoneMetricFormat::DurationSeconds,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn idm_metric_to_api(node: MetricNode) -> GuestMetricNode {
|
||||
pub fn idm_metric_to_api(node: MetricNode) -> ZoneMetricNode {
|
||||
let format = node.format();
|
||||
GuestMetricNode {
|
||||
ZoneMetricNode {
|
||||
name: node.name,
|
||||
value: node.value,
|
||||
format: idm_metric_format_to_api(format).into(),
|
||||
|
@ -1,348 +0,0 @@
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap},
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use krata::v1::{
|
||||
common::{Guest, GuestErrorInfo, GuestExitInfo, GuestNetworkState, GuestState, GuestStatus},
|
||||
control::GuestChangedEvent,
|
||||
};
|
||||
use krataoci::packer::service::OciPackerService;
|
||||
use kratart::{GuestInfo, Runtime};
|
||||
use log::{error, info, trace, warn};
|
||||
use tokio::{
|
||||
select,
|
||||
sync::{
|
||||
mpsc::{channel, Receiver, Sender},
|
||||
Mutex, RwLock,
|
||||
},
|
||||
task::JoinHandle,
|
||||
time::sleep,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
db::GuestStore,
|
||||
event::{DaemonEvent, DaemonEventContext},
|
||||
glt::GuestLookupTable,
|
||||
};
|
||||
|
||||
use self::start::GuestStarter;
|
||||
|
||||
mod start;
|
||||
|
||||
const PARALLEL_LIMIT: u32 = 5;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum GuestReconcilerResult {
|
||||
Unchanged,
|
||||
Changed { rerun: bool },
|
||||
}
|
||||
|
||||
struct GuestReconcilerEntry {
|
||||
task: JoinHandle<()>,
|
||||
sender: Sender<()>,
|
||||
}
|
||||
|
||||
impl Drop for GuestReconcilerEntry {
|
||||
fn drop(&mut self) {
|
||||
self.task.abort();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GuestReconciler {
|
||||
glt: GuestLookupTable,
|
||||
guests: GuestStore,
|
||||
events: DaemonEventContext,
|
||||
runtime: Runtime,
|
||||
packer: OciPackerService,
|
||||
kernel_path: PathBuf,
|
||||
initrd_path: PathBuf,
|
||||
tasks: Arc<Mutex<HashMap<Uuid, GuestReconcilerEntry>>>,
|
||||
guest_reconciler_notify: Sender<Uuid>,
|
||||
reconcile_lock: Arc<RwLock<()>>,
|
||||
}
|
||||
|
||||
impl GuestReconciler {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
glt: GuestLookupTable,
|
||||
guests: GuestStore,
|
||||
events: DaemonEventContext,
|
||||
runtime: Runtime,
|
||||
packer: OciPackerService,
|
||||
guest_reconciler_notify: Sender<Uuid>,
|
||||
kernel_path: PathBuf,
|
||||
initrd_path: PathBuf,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
glt,
|
||||
guests,
|
||||
events,
|
||||
runtime,
|
||||
packer,
|
||||
kernel_path,
|
||||
initrd_path,
|
||||
tasks: Arc::new(Mutex::new(HashMap::new())),
|
||||
guest_reconciler_notify,
|
||||
reconcile_lock: Arc::new(RwLock::with_max_readers((), PARALLEL_LIMIT)),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn launch(self, mut notify: Receiver<Uuid>) -> Result<JoinHandle<()>> {
|
||||
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 guest reconciler task {}: {}", uuid, error);
|
||||
}
|
||||
|
||||
let map = self.tasks.lock().await;
|
||||
if let Some(entry) = map.get(&uuid) {
|
||||
if let Err(error) = entry.sender.send(()).await {
|
||||
error!("failed to notify guest reconciler task {}: {}", uuid, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_ = sleep(Duration::from_secs(5)) => {
|
||||
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.reconcile_lock.write().await;
|
||||
trace!("reconciling runtime");
|
||||
let runtime_guests = self.runtime.list().await?;
|
||||
let stored_guests = self.guests.list().await?;
|
||||
|
||||
let non_existent_guests = runtime_guests
|
||||
.iter()
|
||||
.filter(|x| !stored_guests.iter().any(|g| *g.0 == x.uuid))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for guest in non_existent_guests {
|
||||
warn!("destroying unknown runtime guest {}", guest.uuid);
|
||||
if let Err(error) = self.runtime.destroy(guest.uuid).await {
|
||||
error!(
|
||||
"failed to destroy unknown runtime guest {}: {}",
|
||||
guest.uuid, error
|
||||
);
|
||||
}
|
||||
self.guests.remove(guest.uuid).await?;
|
||||
}
|
||||
|
||||
for (uuid, mut stored_guest) in stored_guests {
|
||||
let previous_guest = stored_guest.clone();
|
||||
let runtime_guest = runtime_guests.iter().find(|x| x.uuid == uuid);
|
||||
match runtime_guest {
|
||||
None => {
|
||||
let mut state = stored_guest.state.as_mut().cloned().unwrap_or_default();
|
||||
if state.status() == GuestStatus::Started {
|
||||
state.status = GuestStatus::Starting.into();
|
||||
}
|
||||
stored_guest.state = Some(state);
|
||||
}
|
||||
|
||||
Some(runtime) => {
|
||||
self.glt.associate(uuid, runtime.domid).await;
|
||||
let mut state = stored_guest.state.as_mut().cloned().unwrap_or_default();
|
||||
if let Some(code) = runtime.state.exit_code {
|
||||
state.status = GuestStatus::Exited.into();
|
||||
state.exit_info = Some(GuestExitInfo { code });
|
||||
} else {
|
||||
state.status = GuestStatus::Started.into();
|
||||
}
|
||||
state.network = Some(guestinfo_to_networkstate(runtime));
|
||||
stored_guest.state = Some(state);
|
||||
}
|
||||
}
|
||||
|
||||
let changed = stored_guest != previous_guest;
|
||||
|
||||
if changed || initial {
|
||||
self.guests.update(uuid, stored_guest).await?;
|
||||
let _ = self.guest_reconciler_notify.try_send(uuid);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn reconcile(&self, uuid: Uuid) -> Result<bool> {
|
||||
let _runtime_reconcile_permit = self.reconcile_lock.read().await;
|
||||
let Some(mut guest) = self.guests.read(uuid).await? else {
|
||||
warn!(
|
||||
"notified of reconcile for guest {} but it didn't exist",
|
||||
uuid
|
||||
);
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
info!("reconciling guest {}", uuid);
|
||||
|
||||
self.events
|
||||
.send(DaemonEvent::GuestChanged(GuestChangedEvent {
|
||||
guest: Some(guest.clone()),
|
||||
}))?;
|
||||
|
||||
let start_status = guest.state.as_ref().map(|x| x.status()).unwrap_or_default();
|
||||
let result = match start_status {
|
||||
GuestStatus::Starting => self.start(uuid, &mut guest).await,
|
||||
GuestStatus::Exited => self.exited(&mut guest).await,
|
||||
GuestStatus::Destroying => self.destroy(uuid, &mut guest).await,
|
||||
_ => Ok(GuestReconcilerResult::Unchanged),
|
||||
};
|
||||
|
||||
let result = match result {
|
||||
Ok(result) => result,
|
||||
Err(error) => {
|
||||
guest.state = Some(guest.state.as_mut().cloned().unwrap_or_default());
|
||||
guest.state.as_mut().unwrap().status = GuestStatus::Failed.into();
|
||||
guest.state.as_mut().unwrap().error_info = Some(GuestErrorInfo {
|
||||
message: error.to_string(),
|
||||
});
|
||||
warn!("failed to start guest {}: {}", guest.id, error);
|
||||
GuestReconcilerResult::Changed { rerun: false }
|
||||
}
|
||||
};
|
||||
|
||||
info!("reconciled guest {}", uuid);
|
||||
|
||||
let status = guest.state.as_ref().map(|x| x.status()).unwrap_or_default();
|
||||
let destroyed = status == GuestStatus::Destroyed;
|
||||
|
||||
let rerun = if let GuestReconcilerResult::Changed { rerun } = result {
|
||||
let event = DaemonEvent::GuestChanged(GuestChangedEvent {
|
||||
guest: Some(guest.clone()),
|
||||
});
|
||||
|
||||
if destroyed {
|
||||
self.guests.remove(uuid).await?;
|
||||
let mut map = self.tasks.lock().await;
|
||||
map.remove(&uuid);
|
||||
} else {
|
||||
self.guests.update(uuid, guest.clone()).await?;
|
||||
}
|
||||
|
||||
self.events.send(event)?;
|
||||
rerun
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
Ok(rerun)
|
||||
}
|
||||
|
||||
async fn start(&self, uuid: Uuid, guest: &mut Guest) -> Result<GuestReconcilerResult> {
|
||||
let starter = GuestStarter {
|
||||
kernel_path: &self.kernel_path,
|
||||
initrd_path: &self.initrd_path,
|
||||
packer: &self.packer,
|
||||
glt: &self.glt,
|
||||
runtime: &self.runtime,
|
||||
};
|
||||
starter.start(uuid, guest).await
|
||||
}
|
||||
|
||||
async fn exited(&self, guest: &mut Guest) -> Result<GuestReconcilerResult> {
|
||||
if let Some(ref mut state) = guest.state {
|
||||
state.set_status(GuestStatus::Destroying);
|
||||
Ok(GuestReconcilerResult::Changed { rerun: true })
|
||||
} else {
|
||||
Ok(GuestReconcilerResult::Unchanged)
|
||||
}
|
||||
}
|
||||
|
||||
async fn destroy(&self, uuid: Uuid, guest: &mut Guest) -> Result<GuestReconcilerResult> {
|
||||
if let Err(error) = self.runtime.destroy(uuid).await {
|
||||
trace!("failed to destroy runtime guest {}: {}", uuid, error);
|
||||
}
|
||||
|
||||
let domid = guest.state.as_ref().map(|x| x.domid);
|
||||
|
||||
if let Some(domid) = domid {
|
||||
self.glt.remove(uuid, domid).await;
|
||||
}
|
||||
|
||||
info!("destroyed guest {}", uuid);
|
||||
guest.state = Some(GuestState {
|
||||
status: GuestStatus::Destroyed.into(),
|
||||
network: None,
|
||||
exit_info: None,
|
||||
error_info: None,
|
||||
host: self.glt.host_uuid().to_string(),
|
||||
domid: domid.unwrap_or(u32::MAX),
|
||||
});
|
||||
Ok(GuestReconcilerResult::Changed { rerun: false })
|
||||
}
|
||||
|
||||
async fn launch_task_if_needed(&self, uuid: Uuid) -> Result<()> {
|
||||
let mut map = self.tasks.lock().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<GuestReconcilerEntry> {
|
||||
let this = self.clone();
|
||||
let (sender, mut receiver) = channel(10);
|
||||
let task = 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 guest {}: {}", uuid, error);
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if rerun {
|
||||
continue 'rerun_loop;
|
||||
}
|
||||
break 'rerun_loop;
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(GuestReconcilerEntry { task, sender })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn guestinfo_to_networkstate(info: &GuestInfo) -> GuestNetworkState {
|
||||
GuestNetworkState {
|
||||
guest_ipv4: info.guest_ipv4.map(|x| x.to_string()).unwrap_or_default(),
|
||||
guest_ipv6: info.guest_ipv6.map(|x| x.to_string()).unwrap_or_default(),
|
||||
guest_mac: info.guest_mac.as_ref().cloned().unwrap_or_default(),
|
||||
gateway_ipv4: info.gateway_ipv4.map(|x| x.to_string()).unwrap_or_default(),
|
||||
gateway_ipv6: info.gateway_ipv6.map(|x| x.to_string()).unwrap_or_default(),
|
||||
gateway_mac: info.gateway_mac.as_ref().cloned().unwrap_or_default(),
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
pub mod guest;
|
||||
pub mod zone;
|
||||
|
374
crates/daemon/src/reconcile/zone/mod.rs
Normal file
374
crates/daemon/src/reconcile/zone/mod.rs
Normal file
@ -0,0 +1,374 @@
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap},
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use krata::v1::{
|
||||
common::{Zone, ZoneErrorInfo, ZoneExitInfo, ZoneNetworkState, ZoneState, ZoneStatus},
|
||||
control::ZoneChangedEvent,
|
||||
};
|
||||
use krataoci::packer::service::OciPackerService;
|
||||
use kratart::{Runtime, ZoneInfo};
|
||||
use log::{error, info, trace, warn};
|
||||
use tokio::{
|
||||
select,
|
||||
sync::{
|
||||
mpsc::{channel, Receiver, Sender},
|
||||
Mutex, RwLock,
|
||||
},
|
||||
task::JoinHandle,
|
||||
time::sleep,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
db::ZoneStore,
|
||||
devices::DaemonDeviceManager,
|
||||
event::{DaemonEvent, DaemonEventContext},
|
||||
zlt::ZoneLookupTable,
|
||||
};
|
||||
|
||||
use self::start::ZoneStarter;
|
||||
|
||||
mod start;
|
||||
|
||||
const PARALLEL_LIMIT: u32 = 5;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ZoneReconcilerResult {
|
||||
Unchanged,
|
||||
Changed { rerun: bool },
|
||||
}
|
||||
|
||||
struct ZoneReconcilerEntry {
|
||||
task: JoinHandle<()>,
|
||||
sender: Sender<()>,
|
||||
}
|
||||
|
||||
impl Drop for ZoneReconcilerEntry {
|
||||
fn drop(&mut self) {
|
||||
self.task.abort();
|
||||
}
|
||||
}
|
||||
|
||||
#[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<Mutex<HashMap<Uuid, ZoneReconcilerEntry>>>,
|
||||
zone_reconciler_notify: Sender<Uuid>,
|
||||
zone_reconcile_lock: Arc<RwLock<()>>,
|
||||
}
|
||||
|
||||
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<Uuid>,
|
||||
kernel_path: PathBuf,
|
||||
initrd_path: PathBuf,
|
||||
modules_path: PathBuf,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
devices,
|
||||
zlt,
|
||||
zones,
|
||||
events,
|
||||
runtime,
|
||||
packer,
|
||||
kernel_path,
|
||||
initrd_path,
|
||||
addons_path: modules_path,
|
||||
tasks: Arc::new(Mutex::new(HashMap::new())),
|
||||
zone_reconciler_notify,
|
||||
zone_reconcile_lock: Arc::new(RwLock::with_max_readers((), PARALLEL_LIMIT)),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn launch(self, mut notify: Receiver<Uuid>) -> Result<JoinHandle<()>> {
|
||||
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.lock().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::<Vec<_>>();
|
||||
|
||||
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 state = stored_zone.state.as_mut().cloned().unwrap_or_default();
|
||||
if state.status() == ZoneStatus::Started {
|
||||
state.status = ZoneStatus::Starting.into();
|
||||
}
|
||||
stored_zone.state = Some(state);
|
||||
}
|
||||
|
||||
Some(runtime) => {
|
||||
self.zlt.associate(uuid, runtime.domid).await;
|
||||
let mut state = stored_zone.state.as_mut().cloned().unwrap_or_default();
|
||||
if let Some(code) = runtime.state.exit_code {
|
||||
state.status = ZoneStatus::Exited.into();
|
||||
state.exit_info = Some(ZoneExitInfo { code });
|
||||
} else {
|
||||
state.status = ZoneStatus::Started.into();
|
||||
}
|
||||
|
||||
for device in &stored_zone
|
||||
.spec
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.devices
|
||||
{
|
||||
device_claims.insert(device.name.clone(), uuid);
|
||||
}
|
||||
|
||||
state.network = Some(zoneinfo_to_networkstate(runtime));
|
||||
stored_zone.state = Some(state);
|
||||
}
|
||||
}
|
||||
|
||||
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<bool> {
|
||||
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_status = zone.state.as_ref().map(|x| x.status()).unwrap_or_default();
|
||||
let result = match start_status {
|
||||
ZoneStatus::Starting => self.start(uuid, &mut zone).await,
|
||||
ZoneStatus::Exited => self.exited(&mut zone).await,
|
||||
ZoneStatus::Destroying => self.destroy(uuid, &mut zone).await,
|
||||
_ => Ok(ZoneReconcilerResult::Unchanged),
|
||||
};
|
||||
|
||||
let result = match result {
|
||||
Ok(result) => result,
|
||||
Err(error) => {
|
||||
zone.state = Some(zone.state.as_mut().cloned().unwrap_or_default());
|
||||
zone.state.as_mut().unwrap().status = ZoneStatus::Failed.into();
|
||||
zone.state.as_mut().unwrap().error_info = Some(ZoneErrorInfo {
|
||||
message: error.to_string(),
|
||||
});
|
||||
warn!("failed to start zone {}: {}", zone.id, error);
|
||||
ZoneReconcilerResult::Changed { rerun: false }
|
||||
}
|
||||
};
|
||||
|
||||
info!("reconciled zone {}", uuid);
|
||||
|
||||
let status = zone.state.as_ref().map(|x| x.status()).unwrap_or_default();
|
||||
let destroyed = status == ZoneStatus::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.lock().await;
|
||||
map.remove(&uuid);
|
||||
} else {
|
||||
self.zones.update(uuid, zone.clone()).await?;
|
||||
}
|
||||
|
||||
self.events.send(event)?;
|
||||
rerun
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
Ok(rerun)
|
||||
}
|
||||
|
||||
async fn start(&self, uuid: Uuid, zone: &mut Zone) -> Result<ZoneReconcilerResult> {
|
||||
let starter = ZoneStarter {
|
||||
devices: &self.devices,
|
||||
kernel_path: &self.kernel_path,
|
||||
initrd_path: &self.initrd_path,
|
||||
addons_path: &self.addons_path,
|
||||
packer: &self.packer,
|
||||
glt: &self.zlt,
|
||||
runtime: &self.runtime,
|
||||
};
|
||||
starter.start(uuid, zone).await
|
||||
}
|
||||
|
||||
async fn exited(&self, zone: &mut Zone) -> Result<ZoneReconcilerResult> {
|
||||
if let Some(ref mut state) = zone.state {
|
||||
state.set_status(ZoneStatus::Destroying);
|
||||
Ok(ZoneReconcilerResult::Changed { rerun: true })
|
||||
} else {
|
||||
Ok(ZoneReconcilerResult::Unchanged)
|
||||
}
|
||||
}
|
||||
|
||||
async fn destroy(&self, uuid: Uuid, zone: &mut Zone) -> Result<ZoneReconcilerResult> {
|
||||
if let Err(error) = self.runtime.destroy(uuid).await {
|
||||
trace!("failed to destroy runtime zone {}: {}", uuid, error);
|
||||
}
|
||||
|
||||
let domid = zone.state.as_ref().map(|x| x.domid);
|
||||
|
||||
if let Some(domid) = domid {
|
||||
self.zlt.remove(uuid, domid).await;
|
||||
}
|
||||
|
||||
info!("destroyed zone {}", uuid);
|
||||
zone.state = Some(ZoneState {
|
||||
status: ZoneStatus::Destroyed.into(),
|
||||
network: None,
|
||||
exit_info: None,
|
||||
error_info: 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.lock().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<ZoneReconcilerEntry> {
|
||||
let this = self.clone();
|
||||
let (sender, mut receiver) = channel(10);
|
||||
let task = 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 { task, sender })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zoneinfo_to_networkstate(info: &ZoneInfo) -> ZoneNetworkState {
|
||||
ZoneNetworkState {
|
||||
zone_ipv4: info.zone_ipv4.map(|x| x.to_string()).unwrap_or_default(),
|
||||
zone_ipv6: info.zone_ipv6.map(|x| x.to_string()).unwrap_or_default(),
|
||||
zone_mac: info.zone_mac.as_ref().cloned().unwrap_or_default(),
|
||||
gateway_ipv4: info.gateway_ipv4.map(|x| x.to_string()).unwrap_or_default(),
|
||||
gateway_ipv6: info.gateway_ipv6.map(|x| x.to_string()).unwrap_or_default(),
|
||||
gateway_mac: info.gateway_mac.as_ref().cloned().unwrap_or_default(),
|
||||
}
|
||||
}
|
@ -1,41 +1,45 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures::StreamExt;
|
||||
use krata::launchcfg::LaunchPackedFormat;
|
||||
use krata::v1::common::GuestOciImageSpec;
|
||||
use krata::v1::common::{guest_image_spec::Image, Guest, GuestState, GuestStatus, OciImageFormat};
|
||||
use krata::v1::common::ZoneOciImageSpec;
|
||||
use krata::v1::common::{OciImageFormat, Zone, ZoneState, ZoneStatus};
|
||||
use krataoci::packer::{service::OciPackerService, OciPackedFormat};
|
||||
use kratart::{launch::GuestLaunchRequest, Runtime};
|
||||
use kratart::launch::{PciBdf, PciDevice, PciRdmReservePolicy};
|
||||
use kratart::{launch::ZoneLaunchRequest, Runtime};
|
||||
use log::info;
|
||||
|
||||
use crate::config::DaemonPciDeviceRdmReservePolicy;
|
||||
use crate::devices::DaemonDeviceManager;
|
||||
use crate::{
|
||||
reconcile::zone::{zoneinfo_to_networkstate, 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;
|
||||
|
||||
use crate::{
|
||||
glt::GuestLookupTable,
|
||||
reconcile::guest::{guestinfo_to_networkstate, GuestReconcilerResult},
|
||||
};
|
||||
|
||||
// if a kernel is >= 100MB, that's kinda scary.
|
||||
const OCI_SPEC_TAR_FILE_MAX_SIZE: usize = 100 * 1024 * 1024;
|
||||
|
||||
pub struct GuestStarter<'a> {
|
||||
pub struct ZoneStarter<'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 glt: &'a GuestLookupTable,
|
||||
pub glt: &'a ZoneLookupTable,
|
||||
pub runtime: &'a Runtime,
|
||||
}
|
||||
|
||||
impl GuestStarter<'_> {
|
||||
impl ZoneStarter<'_> {
|
||||
pub async fn oci_spec_tar_read_file(
|
||||
&self,
|
||||
file: &Path,
|
||||
oci: &GuestOciImageSpec,
|
||||
oci: &ZoneOciImageSpec,
|
||||
) -> Result<Vec<u8>> {
|
||||
if oci.format() != OciImageFormat::Tar {
|
||||
return Err(anyhow!(
|
||||
@ -58,13 +62,6 @@ impl GuestStarter<'_> {
|
||||
while let Some(entry) = entries.next().await {
|
||||
let mut entry = entry?;
|
||||
let path = entry.path()?;
|
||||
if entry.header().size()? as usize > OCI_SPEC_TAR_FILE_MAX_SIZE {
|
||||
return Err(anyhow!(
|
||||
"file {} in image {} is larger than the size limit",
|
||||
file.to_string_lossy(),
|
||||
oci.digest
|
||||
));
|
||||
}
|
||||
if path == file {
|
||||
let mut buffer = Vec::new();
|
||||
entry.read_to_end(&mut buffer).await?;
|
||||
@ -78,9 +75,9 @@ impl GuestStarter<'_> {
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn start(&self, uuid: Uuid, guest: &mut Guest) -> Result<GuestReconcilerResult> {
|
||||
let Some(ref spec) = guest.spec else {
|
||||
return Err(anyhow!("guest spec not specified"));
|
||||
pub async fn start(&self, uuid: Uuid, zone: &mut Zone) -> Result<ZoneReconcilerResult> {
|
||||
let Some(ref spec) = zone.spec else {
|
||||
return Err(anyhow!("zone spec not specified"));
|
||||
};
|
||||
|
||||
let Some(ref image) = spec.image else {
|
||||
@ -103,7 +100,7 @@ impl GuestStarter<'_> {
|
||||
OciImageFormat::Squashfs => OciPackedFormat::Squashfs,
|
||||
OciImageFormat::Erofs => OciPackedFormat::Erofs,
|
||||
OciImageFormat::Tar => {
|
||||
return Err(anyhow!("tar image format is not supported for guests"));
|
||||
return Err(anyhow!("tar image format is not supported for zones"));
|
||||
}
|
||||
},
|
||||
)
|
||||
@ -135,9 +132,51 @@ impl GuestStarter<'_> {
|
||||
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 info = self
|
||||
.runtime
|
||||
.launch(GuestLaunchRequest {
|
||||
.launch(ZoneLaunchRequest {
|
||||
format: LaunchPackedFormat::Squashfs,
|
||||
uuid: Some(uuid),
|
||||
name: if spec.name.is_empty() {
|
||||
@ -150,6 +189,7 @@ impl GuestStarter<'_> {
|
||||
initrd,
|
||||
vcpus: spec.vcpus,
|
||||
mem: spec.mem,
|
||||
pcis,
|
||||
env: task
|
||||
.environment
|
||||
.iter()
|
||||
@ -157,19 +197,21 @@ impl GuestStarter<'_> {
|
||||
.collect::<HashMap<_, _>>(),
|
||||
run: empty_vec_optional(task.command.clone()),
|
||||
debug: false,
|
||||
addons_image: Some(self.addons_path.to_path_buf()),
|
||||
})
|
||||
.await?;
|
||||
self.glt.associate(uuid, info.domid).await;
|
||||
info!("started guest {}", uuid);
|
||||
guest.state = Some(GuestState {
|
||||
status: GuestStatus::Started.into(),
|
||||
network: Some(guestinfo_to_networkstate(&info)),
|
||||
info!("started zone {}", uuid);
|
||||
zone.state = Some(ZoneState {
|
||||
status: ZoneStatus::Started.into(),
|
||||
network: Some(zoneinfo_to_networkstate(&info)),
|
||||
exit_info: None,
|
||||
error_info: None,
|
||||
host: self.glt.host_uuid().to_string(),
|
||||
domid: info.domid,
|
||||
});
|
||||
Ok(GuestReconcilerResult::Changed { rerun: false })
|
||||
success.store(true, Ordering::Release);
|
||||
Ok(ZoneReconcilerResult::Changed { rerun: false })
|
||||
}
|
||||
}
|
||||
|
@ -3,18 +3,18 @@ use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
struct GuestLookupTableState {
|
||||
struct ZoneLookupTableState {
|
||||
domid_to_uuid: HashMap<u32, Uuid>,
|
||||
uuid_to_domid: HashMap<Uuid, u32>,
|
||||
}
|
||||
|
||||
impl GuestLookupTableState {
|
||||
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);
|
||||
GuestLookupTableState {
|
||||
ZoneLookupTableState {
|
||||
domid_to_uuid,
|
||||
uuid_to_domid,
|
||||
}
|
||||
@ -22,18 +22,18 @@ impl GuestLookupTableState {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GuestLookupTable {
|
||||
pub struct ZoneLookupTable {
|
||||
host_domid: u32,
|
||||
host_uuid: Uuid,
|
||||
state: Arc<RwLock<GuestLookupTableState>>,
|
||||
state: Arc<RwLock<ZoneLookupTableState>>,
|
||||
}
|
||||
|
||||
impl GuestLookupTable {
|
||||
impl ZoneLookupTable {
|
||||
pub fn new(host_domid: u32, host_uuid: Uuid) -> Self {
|
||||
GuestLookupTable {
|
||||
ZoneLookupTable {
|
||||
host_domid,
|
||||
host_uuid,
|
||||
state: Arc::new(RwLock::new(GuestLookupTableState::new(host_uuid))),
|
||||
state: Arc::new(RwLock::new(ZoneLookupTableState::new(host_uuid))),
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +0,0 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use env_logger::Env;
|
||||
use krataguest::{death, init::GuestInit};
|
||||
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();
|
||||
if env::var("KRATA_UNSAFE_ALWAYS_ALLOW_INIT").unwrap_or("0".to_string()) != "1" {
|
||||
let pid = std::process::id();
|
||||
if pid > 3 {
|
||||
return Err(anyhow!(
|
||||
"not running because the pid of {} indicates this is probably not \
|
||||
the right context for the init daemon. \
|
||||
run with KRATA_UNSAFE_ALWAYS_ALLOW_INIT=1 to bypass this check",
|
||||
pid
|
||||
));
|
||||
}
|
||||
}
|
||||
let mut guest = GuestInit::new();
|
||||
if let Err(error) = guest.init().await {
|
||||
error!("failed to initialize guest: {}", error);
|
||||
death(127).await?;
|
||||
return Ok(());
|
||||
}
|
||||
death(1).await?;
|
||||
Ok(())
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "krata"
|
||||
description = "Client library and common services for the krata hypervisor."
|
||||
description = "Client library and common services for the krata isolation engine"
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
homepage.workspace = true
|
||||
@ -15,6 +15,7 @@ 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 }
|
||||
@ -27,6 +28,8 @@ tower = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
hyper = { workspace = true }
|
||||
hyper-util = { workspace = true }
|
||||
nix = { workspace = true, features = ["term"] }
|
||||
|
||||
[build-dependencies]
|
||||
|
@ -8,28 +8,29 @@ option java_outer_classname = "CommonProto";
|
||||
|
||||
import "google/protobuf/struct.proto";
|
||||
|
||||
message Guest {
|
||||
message Zone {
|
||||
string id = 1;
|
||||
GuestSpec spec = 2;
|
||||
GuestState state = 3;
|
||||
ZoneSpec spec = 2;
|
||||
ZoneState state = 3;
|
||||
}
|
||||
|
||||
message GuestSpec {
|
||||
message ZoneSpec {
|
||||
string name = 1;
|
||||
GuestImageSpec image = 2;
|
||||
ZoneImageSpec image = 2;
|
||||
// If not specified, defaults to the daemon default kernel.
|
||||
GuestImageSpec kernel = 3;
|
||||
ZoneImageSpec kernel = 3;
|
||||
// If not specified, defaults to the daemon default initrd.
|
||||
GuestImageSpec initrd = 4;
|
||||
ZoneImageSpec initrd = 4;
|
||||
uint32 vcpus = 5;
|
||||
uint64 mem = 6;
|
||||
GuestTaskSpec task = 7;
|
||||
repeated GuestSpecAnnotation annotations = 8;
|
||||
ZoneTaskSpec task = 7;
|
||||
repeated ZoneSpecAnnotation annotations = 8;
|
||||
repeated ZoneSpecDevice devices = 9;
|
||||
}
|
||||
|
||||
message GuestImageSpec {
|
||||
message ZoneImageSpec {
|
||||
oneof image {
|
||||
GuestOciImageSpec oci = 1;
|
||||
ZoneOciImageSpec oci = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,73 +42,77 @@ enum OciImageFormat {
|
||||
OCI_IMAGE_FORMAT_TAR = 3;
|
||||
}
|
||||
|
||||
message GuestOciImageSpec {
|
||||
message ZoneOciImageSpec {
|
||||
string digest = 1;
|
||||
OciImageFormat format = 2;
|
||||
}
|
||||
|
||||
message GuestTaskSpec {
|
||||
repeated GuestTaskSpecEnvVar environment = 1;
|
||||
message ZoneTaskSpec {
|
||||
repeated ZoneTaskSpecEnvVar environment = 1;
|
||||
repeated string command = 2;
|
||||
string working_directory = 3;
|
||||
}
|
||||
|
||||
message GuestTaskSpecEnvVar {
|
||||
message ZoneTaskSpecEnvVar {
|
||||
string key = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message GuestSpecAnnotation {
|
||||
message ZoneSpecAnnotation {
|
||||
string key = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message GuestState {
|
||||
GuestStatus status = 1;
|
||||
GuestNetworkState network = 2;
|
||||
GuestExitInfo exit_info = 3;
|
||||
GuestErrorInfo error_info = 4;
|
||||
message ZoneSpecDevice {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message ZoneState {
|
||||
ZoneStatus status = 1;
|
||||
ZoneNetworkState network = 2;
|
||||
ZoneExitInfo exit_info = 3;
|
||||
ZoneErrorInfo error_info = 4;
|
||||
string host = 5;
|
||||
uint32 domid = 6;
|
||||
}
|
||||
|
||||
enum GuestStatus {
|
||||
GUEST_STATUS_UNKNOWN = 0;
|
||||
GUEST_STATUS_STARTING = 1;
|
||||
GUEST_STATUS_STARTED = 2;
|
||||
GUEST_STATUS_EXITED = 3;
|
||||
GUEST_STATUS_DESTROYING = 4;
|
||||
GUEST_STATUS_DESTROYED = 5;
|
||||
GUEST_STATUS_FAILED = 6;
|
||||
enum ZoneStatus {
|
||||
ZONE_STATUS_UNKNOWN = 0;
|
||||
ZONE_STATUS_STARTING = 1;
|
||||
ZONE_STATUS_STARTED = 2;
|
||||
ZONE_STATUS_EXITED = 3;
|
||||
ZONE_STATUS_DESTROYING = 4;
|
||||
ZONE_STATUS_DESTROYED = 5;
|
||||
ZONE_STATUS_FAILED = 6;
|
||||
}
|
||||
|
||||
message GuestNetworkState {
|
||||
string guest_ipv4 = 1;
|
||||
string guest_ipv6 = 2;
|
||||
string guest_mac = 3;
|
||||
message ZoneNetworkState {
|
||||
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 GuestExitInfo {
|
||||
message ZoneExitInfo {
|
||||
int32 code = 1;
|
||||
}
|
||||
|
||||
message GuestErrorInfo {
|
||||
message ZoneErrorInfo {
|
||||
string message = 1;
|
||||
}
|
||||
|
||||
message GuestMetricNode {
|
||||
message ZoneMetricNode {
|
||||
string name = 1;
|
||||
google.protobuf.Value value = 2;
|
||||
GuestMetricFormat format = 3;
|
||||
repeated GuestMetricNode children = 4;
|
||||
ZoneMetricFormat format = 3;
|
||||
repeated ZoneMetricNode children = 4;
|
||||
}
|
||||
|
||||
enum GuestMetricFormat {
|
||||
GUEST_METRIC_FORMAT_UNKNOWN = 0;
|
||||
GUEST_METRIC_FORMAT_BYTES = 1;
|
||||
GUEST_METRIC_FORMAT_INTEGER = 2;
|
||||
GUEST_METRIC_FORMAT_DURATION_SECONDS = 3;
|
||||
enum ZoneMetricFormat {
|
||||
ZONE_METRIC_FORMAT_UNKNOWN = 0;
|
||||
ZONE_METRIC_FORMAT_BYTES = 1;
|
||||
ZONE_METRIC_FORMAT_INTEGER = 2;
|
||||
ZONE_METRIC_FORMAT_DURATION_SECONDS = 3;
|
||||
}
|
||||
|
@ -12,20 +12,24 @@ import "krata/v1/common.proto";
|
||||
service ControlService {
|
||||
rpc IdentifyHost(IdentifyHostRequest) returns (IdentifyHostReply);
|
||||
|
||||
rpc CreateGuest(CreateGuestRequest) returns (CreateGuestReply);
|
||||
rpc DestroyGuest(DestroyGuestRequest) returns (DestroyGuestReply);
|
||||
rpc ResolveGuest(ResolveGuestRequest) returns (ResolveGuestReply);
|
||||
rpc ListGuests(ListGuestsRequest) returns (ListGuestsReply);
|
||||
rpc CreateZone(CreateZoneRequest) returns (CreateZoneReply);
|
||||
rpc DestroyZone(DestroyZoneRequest) returns (DestroyZoneReply);
|
||||
rpc ResolveZone(ResolveZoneRequest) returns (ResolveZoneReply);
|
||||
rpc ListZones(ListZonesRequest) returns (ListZonesReply);
|
||||
rpc ListDevices(ListDevicesRequest) returns (ListDevicesReply);
|
||||
|
||||
rpc ExecGuest(stream ExecGuestRequest) returns (stream ExecGuestReply);
|
||||
rpc ExecZone(stream ExecZoneRequest) returns (stream ExecZoneReply);
|
||||
|
||||
rpc AttachZoneConsole(stream ZoneConsoleRequest) returns (stream ZoneConsoleReply);
|
||||
rpc ReadZoneMetrics(ReadZoneMetricsRequest) returns (ReadZoneMetricsReply);
|
||||
|
||||
rpc ConsoleData(stream ConsoleDataRequest) returns (stream ConsoleDataReply);
|
||||
rpc ReadGuestMetrics(ReadGuestMetricsRequest) returns (ReadGuestMetricsReply);
|
||||
|
||||
rpc SnoopIdm(SnoopIdmRequest) returns (stream SnoopIdmReply);
|
||||
rpc WatchEvents(WatchEventsRequest) returns (stream WatchEventsReply);
|
||||
|
||||
rpc PullImage(PullImageRequest) returns (stream PullImageReply);
|
||||
|
||||
rpc GetHostCpuTopology(HostCpuTopologyRequest) returns (HostCpuTopologyReply);
|
||||
rpc SetHostPowerManagementPolicy(HostPowerManagementPolicy) returns (HostPowerManagementPolicy);
|
||||
}
|
||||
|
||||
message IdentifyHostRequest {}
|
||||
@ -36,41 +40,41 @@ message IdentifyHostReply {
|
||||
string krata_version = 3;
|
||||
}
|
||||
|
||||
message CreateGuestRequest {
|
||||
krata.v1.common.GuestSpec spec = 1;
|
||||
message CreateZoneRequest {
|
||||
krata.v1.common.ZoneSpec spec = 1;
|
||||
}
|
||||
|
||||
message CreateGuestReply {
|
||||
string guest_id = 1;
|
||||
message CreateZoneReply {
|
||||
string Zone_id = 1;
|
||||
}
|
||||
|
||||
message DestroyGuestRequest {
|
||||
string guest_id = 1;
|
||||
message DestroyZoneRequest {
|
||||
string Zone_id = 1;
|
||||
}
|
||||
|
||||
message DestroyGuestReply {}
|
||||
message DestroyZoneReply {}
|
||||
|
||||
message ResolveGuestRequest {
|
||||
message ResolveZoneRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message ResolveGuestReply {
|
||||
krata.v1.common.Guest guest = 1;
|
||||
message ResolveZoneReply {
|
||||
krata.v1.common.Zone Zone = 1;
|
||||
}
|
||||
|
||||
message ListGuestsRequest {}
|
||||
message ListZonesRequest {}
|
||||
|
||||
message ListGuestsReply {
|
||||
repeated krata.v1.common.Guest guests = 1;
|
||||
message ListZonesReply {
|
||||
repeated krata.v1.common.Zone Zones = 1;
|
||||
}
|
||||
|
||||
message ExecGuestRequest {
|
||||
string guest_id = 1;
|
||||
krata.v1.common.GuestTaskSpec task = 2;
|
||||
message ExecZoneRequest {
|
||||
string Zone_id = 1;
|
||||
krata.v1.common.ZoneTaskSpec task = 2;
|
||||
bytes data = 3;
|
||||
}
|
||||
|
||||
message ExecGuestReply {
|
||||
message ExecZoneReply {
|
||||
bool exited = 1;
|
||||
string error = 2;
|
||||
int32 exit_code = 3;
|
||||
@ -78,12 +82,12 @@ message ExecGuestReply {
|
||||
bytes stderr = 5;
|
||||
}
|
||||
|
||||
message ConsoleDataRequest {
|
||||
string guest_id = 1;
|
||||
message ZoneConsoleRequest {
|
||||
string Zone_id = 1;
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
message ConsoleDataReply {
|
||||
message ZoneConsoleReply {
|
||||
bytes data = 1;
|
||||
}
|
||||
|
||||
@ -91,20 +95,20 @@ message WatchEventsRequest {}
|
||||
|
||||
message WatchEventsReply {
|
||||
oneof event {
|
||||
GuestChangedEvent guest_changed = 1;
|
||||
ZoneChangedEvent Zone_changed = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message GuestChangedEvent {
|
||||
krata.v1.common.Guest guest = 1;
|
||||
message ZoneChangedEvent {
|
||||
krata.v1.common.Zone Zone = 1;
|
||||
}
|
||||
|
||||
message ReadGuestMetricsRequest {
|
||||
string guest_id = 1;
|
||||
message ReadZoneMetricsRequest {
|
||||
string Zone_id = 1;
|
||||
}
|
||||
|
||||
message ReadGuestMetricsReply {
|
||||
krata.v1.common.GuestMetricNode root = 1;
|
||||
message ReadZoneMetricsReply {
|
||||
krata.v1.common.ZoneMetricNode root = 1;
|
||||
}
|
||||
|
||||
message SnoopIdmRequest {}
|
||||
@ -180,6 +184,7 @@ message PullImageRequest {
|
||||
string image = 1;
|
||||
krata.v1.common.OciImageFormat format = 2;
|
||||
bool overwrite_cache = 3;
|
||||
bool update = 4;
|
||||
}
|
||||
|
||||
message PullImageReply {
|
||||
@ -187,3 +192,42 @@ message PullImageReply {
|
||||
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 HostCpuTopologyRequest {}
|
||||
|
||||
message HostCpuTopologyReply {
|
||||
repeated HostCpuTopologyInfo cpus = 1;
|
||||
}
|
||||
|
||||
message HostPowerManagementPolicyRequest {}
|
||||
|
||||
message HostPowerManagementPolicy {
|
||||
string scheduler = 1;
|
||||
bool smt_awareness = 2;
|
||||
}
|
||||
|
@ -1,14 +1,10 @@
|
||||
#[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;
|
||||
#[cfg(unix)]
|
||||
use tokio::net::UnixStream;
|
||||
#[cfg(unix)]
|
||||
use tonic::transport::Uri;
|
||||
use tonic::transport::{Channel, ClientTlsConfig, Endpoint};
|
||||
#[cfg(unix)]
|
||||
use tower::service_fn;
|
||||
|
||||
pub struct ControlClientProvider {}
|
||||
|
||||
@ -52,10 +48,7 @@ impl ControlClientProvider {
|
||||
async fn dial_unix_socket(path: String) -> Result<Channel> {
|
||||
// This URL is not actually used but is required to be specified.
|
||||
Ok(Endpoint::try_from(format!("unix://localhost/{}", path))?
|
||||
.connect_with_connector(service_fn(|uri: Uri| {
|
||||
let path = uri.path().to_string();
|
||||
UnixStream::connect(path)
|
||||
}))
|
||||
.connect_with_connector(HyperUnixConnector {})
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use log::{debug, error};
|
||||
use nix::sys::termios::{cfmakeraw, tcgetattr, tcsetattr, SetArg};
|
||||
use prost::Message;
|
||||
@ -96,10 +97,12 @@ impl IdmBackend for IdmFileBackend {
|
||||
|
||||
async fn send(&mut self, packet: IdmTransportPacket) -> Result<()> {
|
||||
let mut file = self.write.lock().await;
|
||||
let data = packet.encode_to_vec();
|
||||
file.write_all(&[0xff, 0xff]).await?;
|
||||
file.write_u32_le(data.len() as u32).await?;
|
||||
file.write_all(&data).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(())
|
||||
}
|
||||
}
|
||||
@ -488,7 +491,7 @@ impl<R: IdmRequest, E: IdmSerializable> IdmClient<R, E> {
|
||||
error!("unable to send idm packet, packet size exceeded (tried to send {} bytes)", length);
|
||||
continue;
|
||||
}
|
||||
backend.send(packet).await?;
|
||||
backend.send(packet.clone()).await?;
|
||||
},
|
||||
|
||||
None => {
|
||||
|
@ -12,6 +12,9 @@ pub mod launchcfg;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod ethtool;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub mod unix;
|
||||
|
||||
pub static DESCRIPTOR_POOL: Lazy<DescriptorPool> = Lazy::new(|| {
|
||||
DescriptorPool::decode(
|
||||
include_bytes!(concat!(env!("OUT_DIR"), "/file_descriptor_set.bin")).as_ref(),
|
||||
|
73
crates/krata/src/unix.rs
Normal file
73
crates/krata/src/unix.rs
Normal file
@ -0,0 +1,73 @@
|
||||
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<Result<(), Error>> {
|
||||
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<Result<usize, Error>> {
|
||||
self.project().stream.poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
self.project().stream.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
self.project().stream.poll_shutdown(cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HyperUnixConnector;
|
||||
|
||||
impl Service<Uri> for HyperUnixConnector {
|
||||
type Response = HyperUnixStream;
|
||||
type Error = Error;
|
||||
#[allow(clippy::type_complexity)]
|
||||
type Future =
|
||||
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + 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<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
15
crates/loopdev/Cargo.toml
Normal file
15
crates/loopdev/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "krata-loopdev"
|
||||
description = "Loop device handling library for krata"
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
[lib]
|
||||
name = "krataloopdev"
|
||||
|
||||
[dependencies]
|
||||
libc.workspace = true
|
348
crates/loopdev/src/lib.rs
Normal file
348
crates/loopdev/src/lib.rs
Normal file
@ -0,0 +1,348 @@
|
||||
use libc::{c_int, ioctl};
|
||||
use std::{
|
||||
fs::{File, OpenOptions},
|
||||
io,
|
||||
os::fd::{AsRawFd, IntoRawFd, RawFd},
|
||||
os::unix::fs::MetadataExt,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
#[cfg(all(not(target_os = "android"), not(target_env = "musl")))]
|
||||
type IoctlRequest = libc::c_ulong;
|
||||
#[cfg(any(target_os = "android", target_env = "musl"))]
|
||||
type IoctlRequest = libc::c_int;
|
||||
|
||||
const LOOP_CONTROL: &str = "/dev/loop-control";
|
||||
const LOOP_PREFIX: &str = "/dev/loop";
|
||||
|
||||
/// Loop control interface IOCTLs.
|
||||
const LOOP_CTL_GET_FREE: IoctlRequest = 0x4C82;
|
||||
|
||||
/// Loop device flags.
|
||||
const LO_FLAGS_READ_ONLY: u32 = 1;
|
||||
const LO_FLAGS_AUTOCLEAR: u32 = 4;
|
||||
const LO_FLAGS_PARTSCAN: u32 = 8;
|
||||
const LO_FLAGS_DIRECT_IO: u32 = 16;
|
||||
|
||||
/// Loop device IOCTLs.
|
||||
const LOOP_SET_FD: IoctlRequest = 0x4C00;
|
||||
const LOOP_CLR_FD: IoctlRequest = 0x4C01;
|
||||
const LOOP_SET_STATUS64: IoctlRequest = 0x4C04;
|
||||
const LOOP_SET_CAPACITY: IoctlRequest = 0x4C07;
|
||||
const LOOP_SET_DIRECT_IO: IoctlRequest = 0x4C08;
|
||||
|
||||
/// Interface which wraps a handle to the loop control device.
|
||||
#[derive(Debug)]
|
||||
pub struct LoopControl {
|
||||
dev_file: File,
|
||||
}
|
||||
|
||||
/// Translate ioctl results to errors if appropriate.
|
||||
fn translate_error(ret: i32) -> io::Result<i32> {
|
||||
if ret < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
impl LoopControl {
|
||||
/// Open the loop control device.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Any errors from physically opening the loop control device are
|
||||
/// bubbled up.
|
||||
pub fn open() -> io::Result<Self> {
|
||||
Ok(Self {
|
||||
dev_file: OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(LOOP_CONTROL)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Requests the next available loop device from the kernel and opens it.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use krataloopdev::LoopControl;
|
||||
/// let lc = LoopControl::open().unwrap();
|
||||
/// let ld = lc.next_free().unwrap();
|
||||
/// println!("{}", ld.path().unwrap().display());
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Any errors from opening the loop device are bubbled up.
|
||||
pub fn next_free(&self) -> io::Result<LoopDevice> {
|
||||
let dev_num = translate_error(unsafe {
|
||||
ioctl(
|
||||
self.dev_file.as_raw_fd() as c_int,
|
||||
LOOP_CTL_GET_FREE as IoctlRequest,
|
||||
)
|
||||
})?;
|
||||
LoopDevice::open(format!("{}{}", LOOP_PREFIX, dev_num))
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface to a loop device itself, e.g. `/dev/loop0`.
|
||||
#[derive(Debug)]
|
||||
pub struct LoopDevice {
|
||||
device: File,
|
||||
}
|
||||
|
||||
impl AsRawFd for LoopDevice {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.device.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoRawFd for LoopDevice {
|
||||
fn into_raw_fd(self) -> RawFd {
|
||||
self.device.into_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl LoopDevice {
|
||||
/// Opens a loop device.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Any errors from opening the underlying physical loop device are bubbled up.
|
||||
pub fn open<P: AsRef<Path>>(dev: P) -> io::Result<Self> {
|
||||
Ok(Self {
|
||||
device: OpenOptions::new().read(true).write(true).open(dev)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Attach a loop device to a file with the given options.
|
||||
pub fn with(&self) -> AttachOptions<'_> {
|
||||
AttachOptions {
|
||||
device: self,
|
||||
info: LoopInfo64::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables or disables Direct I/O mode.
|
||||
pub fn set_direct_io(&self, direct_io: bool) -> io::Result<()> {
|
||||
translate_error(unsafe {
|
||||
ioctl(
|
||||
self.device.as_raw_fd() as c_int,
|
||||
LOOP_SET_DIRECT_IO as IoctlRequest,
|
||||
if direct_io { 1 } else { 0 },
|
||||
)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Attach the loop device to a fully-mapped file.
|
||||
pub fn attach_file<P: AsRef<Path>>(&self, backing_file: P) -> io::Result<()> {
|
||||
let info = LoopInfo64 {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Self::attach_with_loop_info(self, backing_file, info)
|
||||
}
|
||||
|
||||
/// Attach the loop device to a file with `LoopInfo64`.
|
||||
fn attach_with_loop_info(
|
||||
&self,
|
||||
backing_file: impl AsRef<Path>,
|
||||
info: LoopInfo64,
|
||||
) -> io::Result<()> {
|
||||
let write_access = (info.lo_flags & LO_FLAGS_READ_ONLY) == 0;
|
||||
let bf = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(write_access)
|
||||
.open(backing_file)?;
|
||||
self.attach_fd_with_loop_info(bf, info)
|
||||
}
|
||||
|
||||
/// Attach the loop device to a file descriptor with `LoopInfo64`.
|
||||
fn attach_fd_with_loop_info(&self, bf: impl AsRawFd, info: LoopInfo64) -> io::Result<()> {
|
||||
translate_error(unsafe {
|
||||
ioctl(
|
||||
self.device.as_raw_fd() as c_int,
|
||||
LOOP_SET_FD as IoctlRequest,
|
||||
bf.as_raw_fd() as c_int,
|
||||
)
|
||||
})?;
|
||||
|
||||
let result = unsafe {
|
||||
ioctl(
|
||||
self.device.as_raw_fd() as c_int,
|
||||
LOOP_SET_STATUS64 as IoctlRequest,
|
||||
&info,
|
||||
)
|
||||
};
|
||||
|
||||
match translate_error(result) {
|
||||
Err(err) => {
|
||||
let _detach_err = self.detach();
|
||||
Err(err)
|
||||
}
|
||||
Ok(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the path for the loop device.
|
||||
pub fn path(&self) -> Option<PathBuf> {
|
||||
let mut p = PathBuf::from("/proc/self/fd");
|
||||
p.push(self.device.as_raw_fd().to_string());
|
||||
std::fs::read_link(&p).ok()
|
||||
}
|
||||
|
||||
/// Detach a loop device.
|
||||
pub fn detach(&self) -> io::Result<()> {
|
||||
translate_error(unsafe {
|
||||
ioctl(
|
||||
self.device.as_raw_fd() as c_int,
|
||||
LOOP_CLR_FD as IoctlRequest,
|
||||
0,
|
||||
)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update a loop device's capacity.
|
||||
pub fn set_capacity(&self) -> io::Result<()> {
|
||||
translate_error(unsafe {
|
||||
ioctl(
|
||||
self.device.as_raw_fd() as c_int,
|
||||
LOOP_SET_CAPACITY as IoctlRequest,
|
||||
0,
|
||||
)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the major device node number.
|
||||
pub fn major(&self) -> io::Result<u32> {
|
||||
self.device
|
||||
.metadata()
|
||||
.map(|m| unsafe { libc::major(m.rdev()) })
|
||||
}
|
||||
|
||||
/// Return the minor device node number.
|
||||
pub fn minor(&self) -> io::Result<u32> {
|
||||
self.device
|
||||
.metadata()
|
||||
.map(|m| unsafe { libc::minor(m.rdev()) })
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone)]
|
||||
pub struct LoopInfo64 {
|
||||
lo_device: u64,
|
||||
lo_inode: u64,
|
||||
lo_rdevice: u64,
|
||||
lo_offset: u64,
|
||||
lo_sizelimit: u64,
|
||||
lo_number: u32,
|
||||
lo_encrypt_type: u32,
|
||||
lo_encrypt_key_size: u32,
|
||||
lo_flags: u32,
|
||||
lo_file_name: [u8; 64],
|
||||
lo_crypt_name: [u8; 64],
|
||||
lo_encrypt_key: [u8; 32],
|
||||
lo_init: [u64; 2],
|
||||
}
|
||||
|
||||
impl Default for LoopInfo64 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
lo_device: 0,
|
||||
lo_inode: 0,
|
||||
lo_rdevice: 0,
|
||||
lo_offset: 0,
|
||||
lo_sizelimit: 0,
|
||||
lo_number: 0,
|
||||
lo_encrypt_type: 0,
|
||||
lo_encrypt_key_size: 0,
|
||||
lo_flags: 0,
|
||||
lo_file_name: [0; 64],
|
||||
lo_crypt_name: [0; 64],
|
||||
lo_encrypt_key: [0; 32],
|
||||
lo_init: [0, 2],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub struct AttachOptions<'d> {
|
||||
device: &'d LoopDevice,
|
||||
info: LoopInfo64,
|
||||
}
|
||||
|
||||
impl AttachOptions<'_> {
|
||||
pub fn offset(mut self, offset: u64) -> Self {
|
||||
self.info.lo_offset = offset;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn size_limit(mut self, size_limit: u64) -> Self {
|
||||
self.info.lo_sizelimit = size_limit;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn read_only(mut self, read_only: bool) -> Self {
|
||||
if read_only {
|
||||
self.info.lo_flags |= LO_FLAGS_READ_ONLY;
|
||||
} else {
|
||||
self.info.lo_flags &= !LO_FLAGS_READ_ONLY;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn autoclear(mut self, autoclear: bool) -> Self {
|
||||
if autoclear {
|
||||
self.info.lo_flags |= LO_FLAGS_AUTOCLEAR;
|
||||
} else {
|
||||
self.info.lo_flags &= !LO_FLAGS_AUTOCLEAR;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn part_scan(mut self, part_scan: bool) -> Self {
|
||||
if part_scan {
|
||||
self.info.lo_flags |= LO_FLAGS_PARTSCAN;
|
||||
} else {
|
||||
self.info.lo_flags &= !LO_FLAGS_PARTSCAN;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_direct_io(mut self, direct_io: bool) -> Self {
|
||||
if direct_io {
|
||||
self.info.lo_flags |= LO_FLAGS_DIRECT_IO;
|
||||
} else {
|
||||
self.info.lo_flags &= !LO_FLAGS_DIRECT_IO;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn direct_io(&self) -> bool {
|
||||
(self.info.lo_flags & LO_FLAGS_DIRECT_IO) == LO_FLAGS_DIRECT_IO
|
||||
}
|
||||
|
||||
pub fn attach(&self, backing_file: impl AsRef<Path>) -> io::Result<()> {
|
||||
self.device
|
||||
.attach_with_loop_info(backing_file, self.info.clone())?;
|
||||
if self.direct_io() {
|
||||
self.device.set_direct_io(self.direct_io())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn attach_fd(&self, backing_file_fd: impl AsRawFd) -> io::Result<()> {
|
||||
self.device
|
||||
.attach_fd_with_loop_info(backing_file_fd, self.info.clone())?;
|
||||
if self.direct_io() {
|
||||
self.device.set_direct_io(self.direct_io())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "krata-network"
|
||||
description = "Networking services for the krata hypervisor."
|
||||
description = "Networking services for the krata isolation engine"
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
homepage.workspace = true
|
||||
@ -16,7 +16,7 @@ clap = { workspace = true }
|
||||
env_logger = { workspace = true }
|
||||
etherparse = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
krata = { path = "../krata", version = "^0.0.10" }
|
||||
krata = { path = "../krata", version = "^0.0.15" }
|
||||
krata-advmac = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
@ -2,10 +2,10 @@ use anyhow::Result;
|
||||
use krata::{
|
||||
events::EventStream,
|
||||
v1::{
|
||||
common::Guest,
|
||||
common::Zone,
|
||||
control::{
|
||||
control_service_client::ControlServiceClient, watch_events_reply::Event,
|
||||
ListGuestsRequest,
|
||||
ListZonesRequest,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -33,7 +33,7 @@ pub struct NetworkSide {
|
||||
pub struct NetworkMetadata {
|
||||
pub domid: u32,
|
||||
pub uuid: Uuid,
|
||||
pub guest: NetworkSide,
|
||||
pub zone: NetworkSide,
|
||||
pub gateway: NetworkSide,
|
||||
}
|
||||
|
||||
@ -60,23 +60,23 @@ impl AutoNetworkWatcher {
|
||||
}
|
||||
|
||||
pub async fn read(&mut self) -> Result<Vec<NetworkMetadata>> {
|
||||
let mut all_guests: HashMap<Uuid, Guest> = HashMap::new();
|
||||
for guest in self
|
||||
let mut all_zones: HashMap<Uuid, Zone> = HashMap::new();
|
||||
for zone in self
|
||||
.control
|
||||
.list_guests(ListGuestsRequest {})
|
||||
.list_zones(ListZonesRequest {})
|
||||
.await?
|
||||
.into_inner()
|
||||
.guests
|
||||
.zones
|
||||
{
|
||||
let Ok(uuid) = Uuid::from_str(&guest.id) else {
|
||||
let Ok(uuid) = Uuid::from_str(&zone.id) else {
|
||||
continue;
|
||||
};
|
||||
all_guests.insert(uuid, guest);
|
||||
all_zones.insert(uuid, zone);
|
||||
}
|
||||
|
||||
let mut networks: Vec<NetworkMetadata> = Vec::new();
|
||||
for (uuid, guest) in &all_guests {
|
||||
let Some(ref state) = guest.state else {
|
||||
for (uuid, zone) in &all_zones {
|
||||
let Some(ref state) = zone.state else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@ -88,15 +88,15 @@ impl AutoNetworkWatcher {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Ok(guest_ipv4_cidr) = Ipv4Cidr::from_str(&network.guest_ipv4) else {
|
||||
let Ok(zone_ipv4_cidr) = Ipv4Cidr::from_str(&network.zone_ipv4) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Ok(guest_ipv6_cidr) = Ipv6Cidr::from_str(&network.guest_ipv6) else {
|
||||
let Ok(zone_ipv6_cidr) = Ipv6Cidr::from_str(&network.zone_ipv6) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Ok(guest_mac) = EthernetAddress::from_str(&network.guest_mac) else {
|
||||
let Ok(zone_mac) = EthernetAddress::from_str(&network.zone_mac) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@ -115,10 +115,10 @@ impl AutoNetworkWatcher {
|
||||
networks.push(NetworkMetadata {
|
||||
domid: state.domid,
|
||||
uuid: *uuid,
|
||||
guest: NetworkSide {
|
||||
ipv4: guest_ipv4_cidr,
|
||||
ipv6: guest_ipv6_cidr,
|
||||
mac: guest_mac,
|
||||
zone: NetworkSide {
|
||||
ipv4: zone_ipv4_cidr,
|
||||
ipv6: zone_ipv6_cidr,
|
||||
mac: zone_mac,
|
||||
},
|
||||
gateway: NetworkSide {
|
||||
ipv4: gateway_ipv4_cidr,
|
||||
@ -175,7 +175,7 @@ impl AutoNetworkWatcher {
|
||||
loop {
|
||||
select! {
|
||||
x = receiver.recv() => match x {
|
||||
Ok(Event::GuestChanged(_)) => {
|
||||
Ok(Event::ZoneChanged(_)) => {
|
||||
break;
|
||||
},
|
||||
|
||||
|
@ -54,11 +54,11 @@ impl NetworkStack<'_> {
|
||||
match what {
|
||||
NetworkStackSelect::Receive(Some(packet)) => {
|
||||
if let Err(error) = self.bridge.to_bridge_sender.try_send(packet.clone()) {
|
||||
trace!("failed to send guest packet to bridge: {}", error);
|
||||
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 guest packet to nat: {}", error);
|
||||
trace!("failed to send zone packet to nat: {}", error);
|
||||
}
|
||||
|
||||
self.udev.rx = Some(packet);
|
||||
@ -137,7 +137,7 @@ impl NetworkBackend {
|
||||
.expect("failed to set ip addresses");
|
||||
});
|
||||
let sockets = SocketSet::new(vec![]);
|
||||
let handle = self.bridge.join(self.metadata.guest.mac).await?;
|
||||
let handle = self.bridge.join(self.metadata.zone.mac).await?;
|
||||
let kdev = AsyncRawSocketChannel::new(mtu, kdev)?;
|
||||
Ok(NetworkStack {
|
||||
tx: tx_receiver,
|
||||
@ -153,12 +153,12 @@ impl NetworkBackend {
|
||||
pub async fn launch(self) -> Result<JoinHandle<()>> {
|
||||
Ok(tokio::task::spawn(async move {
|
||||
info!(
|
||||
"launched network backend for krata guest {}",
|
||||
"launched network backend for krata zone {}",
|
||||
self.metadata.uuid
|
||||
);
|
||||
if let Err(error) = self.run().await {
|
||||
warn!(
|
||||
"network backend for krata guest {} failed: {}",
|
||||
"network backend for krata zone {} failed: {}",
|
||||
self.metadata.uuid, error
|
||||
);
|
||||
}
|
||||
@ -169,7 +169,7 @@ impl NetworkBackend {
|
||||
impl Drop for NetworkBackend {
|
||||
fn drop(&mut self) {
|
||||
info!(
|
||||
"destroyed network backend for krata guest {}",
|
||||
"destroyed network backend for krata zone {}",
|
||||
self.metadata.uuid
|
||||
);
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ use hbridge::HostBridge;
|
||||
use krata::{
|
||||
client::ControlClientProvider,
|
||||
dial::ControlDialAddress,
|
||||
v1::{common::Guest, control::control_service_client::ControlServiceClient},
|
||||
v1::{common::Zone, control::control_service_client::ControlServiceClient},
|
||||
};
|
||||
use log::warn;
|
||||
use tokio::{task::JoinHandle, time::sleep};
|
||||
@ -33,7 +33,7 @@ pub const EXTRA_MTU: usize = 20;
|
||||
|
||||
pub struct NetworkService {
|
||||
pub control: ControlServiceClient<Channel>,
|
||||
pub guests: HashMap<Uuid, Guest>,
|
||||
pub zones: HashMap<Uuid, Zone>,
|
||||
pub backends: HashMap<Uuid, JoinHandle<()>>,
|
||||
pub bridge: VirtualBridge,
|
||||
pub hbridge: HostBridge,
|
||||
@ -47,7 +47,7 @@ impl NetworkService {
|
||||
HostBridge::new(HOST_BRIDGE_MTU + EXTRA_MTU, "krata0".to_string(), &bridge).await?;
|
||||
Ok(NetworkService {
|
||||
control,
|
||||
guests: HashMap::new(),
|
||||
zones: HashMap::new(),
|
||||
backends: HashMap::new(),
|
||||
bridge,
|
||||
hbridge,
|
||||
@ -99,7 +99,7 @@ impl NetworkService {
|
||||
|
||||
Err((metadata, error)) => {
|
||||
warn!(
|
||||
"failed to launch network backend for krata guest {}: {}",
|
||||
"failed to launch network backend for krata zone {}: {}",
|
||||
metadata.uuid, error
|
||||
);
|
||||
failed.push(metadata.uuid);
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "krata-oci"
|
||||
description = "OCI services for the krata hypervisor."
|
||||
description = "OCI services for the krata isolation engine"
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
homepage.workspace = true
|
||||
|
@ -37,7 +37,13 @@ async fn main() -> Result<()> {
|
||||
});
|
||||
let service = OciPackerService::new(seed, &cache_dir, OciPlatform::current()).await?;
|
||||
let packed = service
|
||||
.request(image.clone(), OciPackedFormat::Squashfs, false, context)
|
||||
.request(
|
||||
image.clone(),
|
||||
OciPackedFormat::Squashfs,
|
||||
false,
|
||||
true,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
println!(
|
||||
"generated squashfs of {} to {}",
|
||||
|
@ -47,7 +47,7 @@ impl Default for ImageName {
|
||||
}
|
||||
|
||||
impl ImageName {
|
||||
pub const DOCKER_HUB_MIRROR: &'static str = "registry.docker.io";
|
||||
pub const DOCKER_HUB_MIRROR: &'static str = "mirror.gcr.io";
|
||||
pub const DEFAULT_IMAGE_TAG: &'static str = "latest";
|
||||
|
||||
pub fn parse(name: &str) -> Result<Self> {
|
||||
|
@ -4,6 +4,7 @@ use crate::{
|
||||
schema::OciSchema,
|
||||
};
|
||||
|
||||
use crate::fetch::OciResolvedImage;
|
||||
use anyhow::Result;
|
||||
use log::{debug, error};
|
||||
use oci_spec::image::{
|
||||
@ -50,6 +51,51 @@ impl OciPackerCache {
|
||||
Ok(index.manifests().clone())
|
||||
}
|
||||
|
||||
pub async fn resolve(
|
||||
&self,
|
||||
name: ImageName,
|
||||
format: OciPackedFormat,
|
||||
) -> Result<Option<OciResolvedImage>> {
|
||||
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<Descriptor> = 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,
|
||||
|
@ -75,13 +75,23 @@ impl OciPackerService {
|
||||
name: ImageName,
|
||||
format: OciPackedFormat,
|
||||
overwrite: bool,
|
||||
pull: bool,
|
||||
progress_context: OciProgressContext,
|
||||
) -> Result<OciPackedImage> {
|
||||
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 = fetcher.resolve(name.clone()).await?;
|
||||
let resolved = if let Some(resolved) = resolved {
|
||||
resolved
|
||||
} else {
|
||||
fetcher.resolve(name.clone()).await?
|
||||
};
|
||||
|
||||
let key = OciPackerTaskKey {
|
||||
digest: resolved.digest.clone(),
|
||||
format,
|
||||
|
@ -138,7 +138,7 @@ impl VfsNode {
|
||||
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_link_name(PathBuf::from(link_name))?;
|
||||
}
|
||||
header.set_size(self.size);
|
||||
Ok(header)
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "krata-runtime"
|
||||
description = "Runtime for running guests on the krata hypervisor."
|
||||
description = "Runtime for managing zones on the krata isolation engine"
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
homepage.workspace = true
|
||||
@ -12,18 +12,22 @@ resolver = "2"
|
||||
anyhow = { workspace = true }
|
||||
backhand = { workspace = true }
|
||||
ipnetwork = { workspace = true }
|
||||
krata = { path = "../krata", version = "^0.0.10" }
|
||||
krata = { path = "../krata", version = "^0.0.15" }
|
||||
krata-advmac = { workspace = true }
|
||||
krata-oci = { path = "../oci", version = "^0.0.10" }
|
||||
krata-oci = { path = "../oci", version = "^0.0.15" }
|
||||
log = { workspace = true }
|
||||
loopdev-3 = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
krata-xenclient = { path = "../xen/xenclient", version = "^0.0.10" }
|
||||
krata-xenevtchn = { path = "../xen/xenevtchn", version = "^0.0.10" }
|
||||
krata-xengnt = { path = "../xen/xengnt", version = "^0.0.10" }
|
||||
krata-xenstore = { path = "../xen/xenstore", version = "^0.0.10" }
|
||||
krata-loopdev = { path = "../loopdev", version = "^0.0.15" }
|
||||
krata-xencall = { path = "../xen/xencall", version = "^0.0.15" }
|
||||
krata-xenclient = { path = "../xen/xenclient", version = "^0.0.15" }
|
||||
krata-xenevtchn = { path = "../xen/xenevtchn", version = "^0.0.15" }
|
||||
krata-xengnt = { path = "../xen/xengnt", version = "^0.0.15" }
|
||||
krata-xenplatform = { path = "../xen/xenplatform", version = "^0.0.15" }
|
||||
krata-xenstore = { path = "../xen/xenstore", version = "^0.0.15" }
|
||||
walkdir = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
|
||||
[lib]
|
||||
name = "kratart"
|
||||
|
@ -1,8 +1,8 @@
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use krataloopdev::{LoopControl, LoopDevice};
|
||||
use log::debug;
|
||||
use loopdev::{LoopControl, LoopDevice};
|
||||
use tokio::time::sleep;
|
||||
use xenclient::BlockDeviceRef;
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use backhand::{FilesystemWriter, NodeHeader};
|
||||
use backhand::compression::Compressor;
|
||||
use backhand::{FilesystemCompressor, FilesystemWriter, NodeHeader};
|
||||
use krata::launchcfg::LaunchInfo;
|
||||
use krataoci::packer::OciPackedImage;
|
||||
use log::trace;
|
||||
@ -8,14 +9,14 @@ use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct ConfigBlock<'a> {
|
||||
pub image: &'a OciPackedImage,
|
||||
pub struct ConfigBlock {
|
||||
pub image: OciPackedImage,
|
||||
pub file: PathBuf,
|
||||
pub dir: PathBuf,
|
||||
}
|
||||
|
||||
impl ConfigBlock<'_> {
|
||||
pub fn new<'a>(uuid: &Uuid, image: &'a OciPackedImage) -> Result<ConfigBlock<'a>> {
|
||||
impl ConfigBlock {
|
||||
pub fn new(uuid: &Uuid, image: OciPackedImage) -> Result<ConfigBlock> {
|
||||
let mut dir = std::env::temp_dir().clone();
|
||||
dir.push(format!("krata-cfg-{}", uuid));
|
||||
fs::create_dir_all(&dir)?;
|
||||
@ -29,6 +30,7 @@ impl ConfigBlock<'_> {
|
||||
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 {
|
||||
|
@ -375,7 +375,10 @@ impl KrataChannelBackendProcessor {
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -389,14 +392,24 @@ impl KrataChannelBackendProcessor {
|
||||
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,
|
||||
)?;
|
||||
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?;
|
||||
|
331
crates/runtime/src/ip.rs
Normal file
331
crates/runtime/src/ip.rs
Normal file
@ -0,0 +1,331 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{Ipv4Addr, Ipv6Addr},
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use ipnetwork::{Ipv4Network, Ipv6Network};
|
||||
use log::error;
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
use xenstore::{XsdClient, XsdInterface};
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct IpVendorState {
|
||||
pub ipv4: HashMap<Ipv4Addr, Uuid>,
|
||||
pub ipv6: HashMap<Ipv6Addr, Uuid>,
|
||||
pub pending_ipv4: HashMap<Ipv4Addr, Uuid>,
|
||||
pub pending_ipv6: HashMap<Ipv6Addr, Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct IpVendor {
|
||||
store: XsdClient,
|
||||
host_uuid: Uuid,
|
||||
ipv4_network: Ipv4Network,
|
||||
ipv6_network: Ipv6Network,
|
||||
gateway_ipv4: Ipv4Addr,
|
||||
gateway_ipv6: Ipv6Addr,
|
||||
state: Arc<RwLock<IpVendorState>>,
|
||||
}
|
||||
|
||||
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<Self> {
|
||||
let mut state = IpVendor::fetch_stored_state(&store).await?;
|
||||
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)),
|
||||
};
|
||||
Ok(vend)
|
||||
}
|
||||
|
||||
async fn fetch_stored_state(store: &XsdClient) -> Result<IpVendorState> {
|
||||
let mut state = IpVendorState::default();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
fn allocate_ipset(
|
||||
state: &mut IpVendorState,
|
||||
uuid: Uuid,
|
||||
ipv4_network: Ipv4Network,
|
||||
ipv6_network: Ipv6Network,
|
||||
) -> Result<(Ipv4Addr, Ipv6Addr)> {
|
||||
let mut found_ipv4: Option<Ipv4Addr> = 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<Ipv6Addr> = 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<IpAssignment> {
|
||||
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<Option<IpAssignment>> {
|
||||
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<IpVendorState> {
|
||||
Ok(self.state.read().await.clone())
|
||||
}
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
use std::collections::HashMap;
|
||||
use std::net::{IpAddr, Ipv6Addr};
|
||||
use std::fs;
|
||||
use std::net::IpAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::{fs, net::Ipv4Addr, str::FromStr};
|
||||
|
||||
use advmac::MacAddr6;
|
||||
use anyhow::{anyhow, Result};
|
||||
use ipnetwork::{IpNetwork, Ipv4Network};
|
||||
use ipnetwork::IpNetwork;
|
||||
use krata::launchcfg::{
|
||||
LaunchInfo, LaunchNetwork, LaunchNetworkIpv4, LaunchNetworkIpv6, LaunchNetworkResolver,
|
||||
LaunchPackedFormat, LaunchRoot,
|
||||
@ -14,14 +15,18 @@ use krataoci::packer::OciPackedImage;
|
||||
use tokio::sync::Semaphore;
|
||||
use uuid::Uuid;
|
||||
use xenclient::{DomainChannel, DomainConfig, DomainDisk, DomainNetworkInterface};
|
||||
use xenstore::XsdInterface;
|
||||
use xenplatform::domain::BaseDomainConfig;
|
||||
|
||||
use crate::cfgblk::ConfigBlock;
|
||||
use crate::RuntimeContext;
|
||||
|
||||
use super::{GuestInfo, GuestState};
|
||||
use super::{ZoneInfo, ZoneState};
|
||||
|
||||
pub struct GuestLaunchRequest {
|
||||
pub use xenclient::{
|
||||
pci::PciBdf, DomainPciDevice as PciDevice, DomainPciRdmReservePolicy as PciRdmReservePolicy,
|
||||
};
|
||||
|
||||
pub struct ZoneLaunchRequest {
|
||||
pub format: LaunchPackedFormat,
|
||||
pub kernel: Vec<u8>,
|
||||
pub initrd: Vec<u8>,
|
||||
@ -31,15 +36,17 @@ pub struct GuestLaunchRequest {
|
||||
pub mem: u64,
|
||||
pub env: HashMap<String, String>,
|
||||
pub run: Option<Vec<String>>,
|
||||
pub pcis: Vec<PciDevice>,
|
||||
pub debug: bool,
|
||||
pub image: OciPackedImage,
|
||||
pub addons_image: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub struct GuestLauncher {
|
||||
pub struct ZoneLauncher {
|
||||
pub launch_semaphore: Arc<Semaphore>,
|
||||
}
|
||||
|
||||
impl GuestLauncher {
|
||||
impl ZoneLauncher {
|
||||
pub fn new(launch_semaphore: Arc<Semaphore>) -> Result<Self> {
|
||||
Ok(Self { launch_semaphore })
|
||||
}
|
||||
@ -47,25 +54,19 @@ impl GuestLauncher {
|
||||
pub async fn launch(
|
||||
&mut self,
|
||||
context: &RuntimeContext,
|
||||
request: GuestLaunchRequest,
|
||||
) -> Result<GuestInfo> {
|
||||
request: ZoneLaunchRequest,
|
||||
) -> Result<ZoneInfo> {
|
||||
let uuid = request.uuid.unwrap_or_else(Uuid::new_v4);
|
||||
let xen_name = format!("krata-{uuid}");
|
||||
let mut gateway_mac = MacAddr6::random();
|
||||
gateway_mac.set_local(true);
|
||||
gateway_mac.set_multicast(false);
|
||||
let mut container_mac = MacAddr6::random();
|
||||
container_mac.set_local(true);
|
||||
container_mac.set_multicast(false);
|
||||
let mut zone_mac = MacAddr6::random();
|
||||
zone_mac.set_local(true);
|
||||
zone_mac.set_multicast(false);
|
||||
|
||||
let _launch_permit = self.launch_semaphore.acquire().await?;
|
||||
let guest_ipv4 = self.allocate_ipv4(context).await?;
|
||||
let guest_ipv6 = container_mac.to_link_local_ipv6();
|
||||
let gateway_ipv4 = "10.75.70.1";
|
||||
let gateway_ipv6 = "fe80::1";
|
||||
let ipv4_network_mask: u32 = 16;
|
||||
let ipv6_network_mask: u32 = 10;
|
||||
|
||||
let mut ip = context.ipvendor.assign(uuid).await?;
|
||||
let launch_config = LaunchInfo {
|
||||
root: LaunchRoot {
|
||||
format: request.format.clone(),
|
||||
@ -80,12 +81,12 @@ impl GuestLauncher {
|
||||
network: Some(LaunchNetwork {
|
||||
link: "eth0".to_string(),
|
||||
ipv4: LaunchNetworkIpv4 {
|
||||
address: format!("{}/{}", guest_ipv4, ipv4_network_mask),
|
||||
gateway: gateway_ipv4.to_string(),
|
||||
address: format!("{}/{}", ip.ipv4, ip.ipv4_prefix),
|
||||
gateway: ip.gateway_ipv4.to_string(),
|
||||
},
|
||||
ipv6: LaunchNetworkIpv6 {
|
||||
address: format!("{}/{}", guest_ipv6, ipv6_network_mask),
|
||||
gateway: gateway_ipv6.to_string(),
|
||||
address: format!("{}/{}", ip.ipv6, ip.ipv6_prefix),
|
||||
gateway: ip.gateway_ipv6.to_string(),
|
||||
},
|
||||
resolver: LaunchNetworkResolver {
|
||||
nameservers: vec![
|
||||
@ -100,8 +101,10 @@ impl GuestLauncher {
|
||||
run: request.run,
|
||||
};
|
||||
|
||||
let cfgblk = ConfigBlock::new(&uuid, &request.image)?;
|
||||
cfgblk.build(&launch_config)?;
|
||||
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
|
||||
@ -109,59 +112,103 @@ impl GuestLauncher {
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("failed to convert image path to string"))?;
|
||||
|
||||
let cfgblk_dir_path = cfgblk
|
||||
.dir
|
||||
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
|
||||
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 cmdline_options = [
|
||||
if request.debug { "debug" } else { "quiet" },
|
||||
"elevator=noop",
|
||||
];
|
||||
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.debug {
|
||||
cmdline_options.push("quiet");
|
||||
}
|
||||
let cmdline = cmdline_options.join(" ");
|
||||
|
||||
let guest_mac_string = container_mac.to_string().replace('-', ":");
|
||||
let zone_mac_string = zone_mac.to_string().replace('-', ":");
|
||||
let gateway_mac_string = gateway_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(",")),
|
||||
(
|
||||
"krata/loops".to_string(),
|
||||
format!(
|
||||
"{}:{}:none,{}:{}:{}",
|
||||
&image_squashfs_loop.path,
|
||||
image_squashfs_path,
|
||||
&cfgblk_squashfs_loop.path,
|
||||
cfgblk_squashfs_path,
|
||||
cfgblk_dir_path,
|
||||
),
|
||||
"krata/network/zone/ipv4".to_string(),
|
||||
format!("{}/{}", ip.ipv4, ip.ipv4_prefix),
|
||||
),
|
||||
(
|
||||
"krata/network/guest/ipv4".to_string(),
|
||||
format!("{}/{}", guest_ipv4, ipv4_network_mask),
|
||||
"krata/network/zone/ipv6".to_string(),
|
||||
format!("{}/{}", ip.ipv6, ip.ipv6_prefix),
|
||||
),
|
||||
(
|
||||
"krata/network/guest/ipv6".to_string(),
|
||||
format!("{}/{}", guest_ipv6, ipv6_network_mask),
|
||||
),
|
||||
(
|
||||
"krata/network/guest/mac".to_string(),
|
||||
guest_mac_string.clone(),
|
||||
"krata/network/zone/mac".to_string(),
|
||||
zone_mac_string.clone(),
|
||||
),
|
||||
(
|
||||
"krata/network/gateway/ipv4".to_string(),
|
||||
format!("{}/{}", gateway_ipv4, ipv4_network_mask),
|
||||
format!("{}/{}", ip.gateway_ipv4, ip.ipv4_prefix),
|
||||
),
|
||||
(
|
||||
"krata/network/gateway/ipv6".to_string(),
|
||||
format!("{}/{}", gateway_ipv6, ipv6_network_mask),
|
||||
format!("{}/{}", ip.gateway_ipv6, ip.ipv6_prefix),
|
||||
),
|
||||
(
|
||||
"krata/network/gateway/mac".to_string(),
|
||||
@ -174,108 +221,65 @@ impl GuestLauncher {
|
||||
}
|
||||
|
||||
let config = DomainConfig {
|
||||
base: BaseDomainConfig {
|
||||
max_vcpus: request.vcpus,
|
||||
mem_mb: request.mem,
|
||||
kernel: request.kernel,
|
||||
initrd: request.initrd,
|
||||
cmdline,
|
||||
uuid,
|
||||
owner_domid: 0,
|
||||
enable_iommu: !request.pcis.is_empty(),
|
||||
},
|
||||
backend_domid: 0,
|
||||
name: xen_name,
|
||||
max_vcpus: request.vcpus,
|
||||
mem_mb: request.mem,
|
||||
kernel: request.kernel,
|
||||
initrd: request.initrd,
|
||||
cmdline,
|
||||
use_console_backend: Some("krata-console".to_string()),
|
||||
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,
|
||||
},
|
||||
],
|
||||
swap_console_backend: Some("krata-console".to_string()),
|
||||
disks,
|
||||
channels: vec![DomainChannel {
|
||||
typ: "krata-channel".to_string(),
|
||||
initialized: false,
|
||||
}],
|
||||
vifs: vec![DomainNetworkInterface {
|
||||
mac: guest_mac_string.clone(),
|
||||
mac: zone_mac_string.clone(),
|
||||
mtu: 1500,
|
||||
bridge: None,
|
||||
script: None,
|
||||
}],
|
||||
pcis: request.pcis.clone(),
|
||||
filesystems: vec![],
|
||||
event_channels: vec![],
|
||||
extra_keys,
|
||||
extra_rw_paths: vec!["krata/guest".to_string()],
|
||||
extra_rw_paths: vec!["krata/zone".to_string()],
|
||||
};
|
||||
match context.xen.create(&config).await {
|
||||
Ok(created) => Ok(GuestInfo {
|
||||
name: request.name.as_ref().map(|x| x.to_string()),
|
||||
uuid,
|
||||
domid: created.domid,
|
||||
image: request.image.digest,
|
||||
loops: vec![],
|
||||
guest_ipv4: Some(IpNetwork::new(
|
||||
IpAddr::V4(guest_ipv4),
|
||||
ipv4_network_mask as u8,
|
||||
)?),
|
||||
guest_ipv6: Some(IpNetwork::new(
|
||||
IpAddr::V6(guest_ipv6),
|
||||
ipv6_network_mask as u8,
|
||||
)?),
|
||||
guest_mac: Some(guest_mac_string.clone()),
|
||||
gateway_ipv4: Some(IpNetwork::new(
|
||||
IpAddr::V4(Ipv4Addr::from_str(gateway_ipv4)?),
|
||||
ipv4_network_mask as u8,
|
||||
)?),
|
||||
gateway_ipv6: Some(IpNetwork::new(
|
||||
IpAddr::V6(Ipv6Addr::from_str(gateway_ipv6)?),
|
||||
ipv6_network_mask as u8,
|
||||
)?),
|
||||
gateway_mac: Some(gateway_mac_string.clone()),
|
||||
state: GuestState { exit_code: None },
|
||||
}),
|
||||
Ok(created) => {
|
||||
ip.commit().await?;
|
||||
Ok(ZoneInfo {
|
||||
name: request.name.as_ref().map(|x| x.to_string()),
|
||||
uuid,
|
||||
domid: created.domid,
|
||||
image: request.image.digest,
|
||||
loops: vec![],
|
||||
zone_ipv4: Some(IpNetwork::new(IpAddr::V4(ip.ipv4), ip.ipv4_prefix)?),
|
||||
zone_ipv6: Some(IpNetwork::new(IpAddr::V6(ip.ipv6), ip.ipv6_prefix)?),
|
||||
zone_mac: Some(zone_mac_string.clone()),
|
||||
gateway_ipv4: Some(IpNetwork::new(
|
||||
IpAddr::V4(ip.gateway_ipv4),
|
||||
ip.ipv4_prefix,
|
||||
)?),
|
||||
gateway_ipv6: Some(IpNetwork::new(
|
||||
IpAddr::V6(ip.gateway_ipv6),
|
||||
ip.ipv6_prefix,
|
||||
)?),
|
||||
gateway_mac: Some(gateway_mac_string.clone()),
|
||||
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);
|
||||
let _ = fs::remove_dir(&cfgblk_dir);
|
||||
Err(error.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn allocate_ipv4(&self, context: &RuntimeContext) -> Result<Ipv4Addr> {
|
||||
let network = Ipv4Network::new(Ipv4Addr::new(10, 75, 80, 0), 24)?;
|
||||
let mut used: Vec<Ipv4Addr> = vec![];
|
||||
for domid_candidate in context.xen.store.list("/local/domain").await? {
|
||||
let dom_path = format!("/local/domain/{}", domid_candidate);
|
||||
let ip_path = format!("{}/krata/network/guest/ipv4", dom_path);
|
||||
let existing_ip = context.xen.store.read_string(&ip_path).await?;
|
||||
if let Some(existing_ip) = existing_ip {
|
||||
let ipv4_network = Ipv4Network::from_str(&existing_ip)?;
|
||||
used.push(ipv4_network.ip());
|
||||
}
|
||||
}
|
||||
|
||||
let mut found: Option<Ipv4Addr> = None;
|
||||
for ip in network.iter() {
|
||||
let last = ip.octets()[3];
|
||||
if last == 0 || last == 255 {
|
||||
continue;
|
||||
}
|
||||
if !used.contains(&ip) {
|
||||
found = Some(ip);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if found.is_none() {
|
||||
return Err(anyhow!(
|
||||
"unable to find ipv4 to allocate to guest, ipv4 addresses are exhausted"
|
||||
));
|
||||
}
|
||||
|
||||
Ok(found.unwrap())
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
use std::{fs, path::PathBuf, str::FromStr, sync::Arc};
|
||||
use std::{fs, net::Ipv4Addr, path::PathBuf, str::FromStr, sync::Arc};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use ipnetwork::IpNetwork;
|
||||
use loopdev::LoopControl;
|
||||
use ip::IpVendor;
|
||||
use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
|
||||
use krataloopdev::LoopControl;
|
||||
use log::error;
|
||||
use tokio::sync::Semaphore;
|
||||
use uuid::Uuid;
|
||||
use xenclient::XenClient;
|
||||
@ -10,56 +12,74 @@ use xenstore::{XsdClient, XsdInterface};
|
||||
|
||||
use self::{
|
||||
autoloop::AutoLoop,
|
||||
launch::{GuestLaunchRequest, GuestLauncher},
|
||||
launch::{ZoneLaunchRequest, ZoneLauncher},
|
||||
power::PowerManagementContext,
|
||||
};
|
||||
|
||||
pub mod autoloop;
|
||||
pub mod cfgblk;
|
||||
pub mod channel;
|
||||
pub mod ip;
|
||||
pub mod launch;
|
||||
pub mod power;
|
||||
|
||||
pub struct GuestLoopInfo {
|
||||
#[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<String>,
|
||||
}
|
||||
|
||||
pub struct GuestState {
|
||||
#[derive(Clone)]
|
||||
pub struct ZoneState {
|
||||
pub exit_code: Option<i32>,
|
||||
}
|
||||
|
||||
pub struct GuestInfo {
|
||||
#[derive(Clone)]
|
||||
pub struct ZoneInfo {
|
||||
pub name: Option<String>,
|
||||
pub uuid: Uuid,
|
||||
pub domid: u32,
|
||||
pub image: String,
|
||||
pub loops: Vec<GuestLoopInfo>,
|
||||
pub guest_ipv4: Option<IpNetwork>,
|
||||
pub guest_ipv6: Option<IpNetwork>,
|
||||
pub guest_mac: Option<String>,
|
||||
pub loops: Vec<ZoneLoopInfo>,
|
||||
pub zone_ipv4: Option<IpNetwork>,
|
||||
pub zone_ipv6: Option<IpNetwork>,
|
||||
pub zone_mac: Option<String>,
|
||||
pub gateway_ipv4: Option<IpNetwork>,
|
||||
pub gateway_ipv6: Option<IpNetwork>,
|
||||
pub gateway_mac: Option<String>,
|
||||
pub state: GuestState,
|
||||
pub state: ZoneState,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RuntimeContext {
|
||||
pub autoloop: AutoLoop,
|
||||
pub xen: XenClient,
|
||||
pub xen: XenClient<RuntimePlatform>,
|
||||
pub ipvendor: IpVendor,
|
||||
}
|
||||
|
||||
impl RuntimeContext {
|
||||
pub async fn new() -> Result<Self> {
|
||||
let xen = XenClient::open(0).await?;
|
||||
pub async fn new(host_uuid: Uuid) -> Result<Self> {
|
||||
let xen = XenClient::new(0, RuntimePlatform::new()).await?;
|
||||
let ipv4_network = Ipv4Network::new(Ipv4Addr::new(10, 75, 80, 0), 24)?;
|
||||
let ipv6_network = Ipv6Network::from_str("fdd4:1476:6c7e::/48")?;
|
||||
let ipvend =
|
||||
IpVendor::new(xen.store.clone(), host_uuid, ipv4_network, ipv6_network).await?;
|
||||
Ok(RuntimeContext {
|
||||
autoloop: AutoLoop::new(LoopControl::open()?),
|
||||
xen,
|
||||
ipvendor: ipvend,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn list(&self) -> Result<Vec<GuestInfo>> {
|
||||
let mut guests: Vec<GuestInfo> = Vec::new();
|
||||
pub async fn list(&self) -> Result<Vec<ZoneInfo>> {
|
||||
let mut zones: Vec<ZoneInfo> = Vec::new();
|
||||
for domid_candidate in self.xen.store.list("/local/domain").await? {
|
||||
if domid_candidate == "0" {
|
||||
continue;
|
||||
@ -95,20 +115,20 @@ impl RuntimeContext {
|
||||
.store
|
||||
.read_string(&format!("{}/krata/loops", &dom_path))
|
||||
.await?;
|
||||
let guest_ipv4 = self
|
||||
let zone_ipv4 = self
|
||||
.xen
|
||||
.store
|
||||
.read_string(&format!("{}/krata/network/guest/ipv4", &dom_path))
|
||||
.read_string(&format!("{}/krata/network/zone/ipv4", &dom_path))
|
||||
.await?;
|
||||
let guest_ipv6 = self
|
||||
let zone_ipv6 = self
|
||||
.xen
|
||||
.store
|
||||
.read_string(&format!("{}/krata/network/guest/ipv6", &dom_path))
|
||||
.read_string(&format!("{}/krata/network/zone/ipv6", &dom_path))
|
||||
.await?;
|
||||
let guest_mac = self
|
||||
let zone_mac = self
|
||||
.xen
|
||||
.store
|
||||
.read_string(&format!("{}/krata/network/guest/mac", &dom_path))
|
||||
.read_string(&format!("{}/krata/network/zone/mac", &dom_path))
|
||||
.await?;
|
||||
let gateway_ipv4 = self
|
||||
.xen
|
||||
@ -126,14 +146,14 @@ impl RuntimeContext {
|
||||
.read_string(&format!("{}/krata/network/gateway/mac", &dom_path))
|
||||
.await?;
|
||||
|
||||
let guest_ipv4 = if let Some(guest_ipv4) = guest_ipv4 {
|
||||
IpNetwork::from_str(&guest_ipv4).ok()
|
||||
let zone_ipv4 = if let Some(zone_ipv4) = zone_ipv4 {
|
||||
IpNetwork::from_str(&zone_ipv4).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let guest_ipv6 = if let Some(guest_ipv6) = guest_ipv6 {
|
||||
IpNetwork::from_str(&guest_ipv6).ok()
|
||||
let zone_ipv6 = if let Some(zone_ipv6) = zone_ipv6 {
|
||||
IpNetwork::from_str(&zone_ipv6).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@ -153,7 +173,7 @@ impl RuntimeContext {
|
||||
let exit_code = self
|
||||
.xen
|
||||
.store
|
||||
.read_string(&format!("{}/krata/guest/exit-code", &dom_path))
|
||||
.read_string(&format!("{}/krata/zone/exit-code", &dom_path))
|
||||
.await?;
|
||||
|
||||
let exit_code: Option<i32> = match exit_code {
|
||||
@ -161,37 +181,37 @@ impl RuntimeContext {
|
||||
None => None,
|
||||
};
|
||||
|
||||
let state = GuestState { exit_code };
|
||||
let state = ZoneState { exit_code };
|
||||
|
||||
let loops = RuntimeContext::parse_loop_set(&loops);
|
||||
guests.push(GuestInfo {
|
||||
zones.push(ZoneInfo {
|
||||
name,
|
||||
uuid,
|
||||
domid,
|
||||
image,
|
||||
loops,
|
||||
guest_ipv4,
|
||||
guest_ipv6,
|
||||
guest_mac,
|
||||
zone_ipv4,
|
||||
zone_ipv6,
|
||||
zone_mac,
|
||||
gateway_ipv4,
|
||||
gateway_ipv6,
|
||||
gateway_mac,
|
||||
state,
|
||||
});
|
||||
}
|
||||
Ok(guests)
|
||||
Ok(zones)
|
||||
}
|
||||
|
||||
pub async fn resolve(&self, uuid: Uuid) -> Result<Option<GuestInfo>> {
|
||||
for guest in self.list().await? {
|
||||
if guest.uuid == uuid {
|
||||
return Ok(Some(guest));
|
||||
pub async fn resolve(&self, uuid: Uuid) -> Result<Option<ZoneInfo>> {
|
||||
for zone in self.list().await? {
|
||||
if zone.uuid == uuid {
|
||||
return Ok(Some(zone));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn parse_loop_set(input: &Option<String>) -> Vec<GuestLoopInfo> {
|
||||
fn parse_loop_set(input: &Option<String>) -> Vec<ZoneLoopInfo> {
|
||||
let Some(input) = input else {
|
||||
return Vec::new();
|
||||
};
|
||||
@ -202,7 +222,7 @@ impl RuntimeContext {
|
||||
.map(|x| (x[0].clone(), x[1].clone(), x[2].clone()))
|
||||
.collect::<Vec<(String, String, String)>>();
|
||||
sets.iter()
|
||||
.map(|(device, file, delete)| GuestLoopInfo {
|
||||
.map(|(device, file, delete)| ZoneLoopInfo {
|
||||
device: device.clone(),
|
||||
file: file.clone(),
|
||||
delete: if delete == "none" {
|
||||
@ -211,27 +231,29 @@ impl RuntimeContext {
|
||||
Some(delete.clone())
|
||||
},
|
||||
})
|
||||
.collect::<Vec<GuestLoopInfo>>()
|
||||
.collect::<Vec<ZoneLoopInfo>>()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Runtime {
|
||||
host_uuid: Uuid,
|
||||
context: RuntimeContext,
|
||||
launch_semaphore: Arc<Semaphore>,
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
pub async fn new() -> Result<Self> {
|
||||
let context = RuntimeContext::new().await?;
|
||||
pub async fn new(host_uuid: Uuid) -> Result<Self> {
|
||||
let context = RuntimeContext::new(host_uuid).await?;
|
||||
Ok(Self {
|
||||
host_uuid,
|
||||
context,
|
||||
launch_semaphore: Arc::new(Semaphore::new(1)),
|
||||
launch_semaphore: Arc::new(Semaphore::new(10)),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn launch(&self, request: GuestLaunchRequest) -> Result<GuestInfo> {
|
||||
let mut launcher = GuestLauncher::new(self.launch_semaphore.clone())?;
|
||||
pub async fn launch(&self, request: ZoneLaunchRequest) -> Result<ZoneInfo> {
|
||||
let mut launcher = ZoneLauncher::new(self.launch_semaphore.clone())?;
|
||||
launcher.launch(&self.context, request).await
|
||||
}
|
||||
|
||||
@ -240,7 +262,7 @@ impl Runtime {
|
||||
.context
|
||||
.resolve(uuid)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("unable to resolve guest: {}", uuid))?;
|
||||
.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?;
|
||||
@ -260,6 +282,11 @@ impl Runtime {
|
||||
return Err(anyhow!("unable to find krata uuid based on the domain",));
|
||||
}
|
||||
let uuid = Uuid::parse_str(&uuid)?;
|
||||
let ip = self
|
||||
.context
|
||||
.ipvendor
|
||||
.read_domain_assignment(uuid, domid)
|
||||
.await?;
|
||||
let loops = store
|
||||
.read_string(format!("{}/krata/loops", dom_path).as_str())
|
||||
.await?;
|
||||
@ -279,14 +306,29 @@ impl Runtime {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ip) = ip {
|
||||
if let Err(error) = self.context.ipvendor.recall(&ip).await {
|
||||
error!(
|
||||
"failed to recall ip assignment for zone {}: {}",
|
||||
uuid, error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(uuid)
|
||||
}
|
||||
|
||||
pub async fn list(&self) -> Result<Vec<GuestInfo>> {
|
||||
pub async fn list(&self) -> Result<Vec<ZoneInfo>> {
|
||||
self.context.list().await
|
||||
}
|
||||
|
||||
pub async fn dupe(&self) -> Result<Runtime> {
|
||||
Runtime::new().await
|
||||
Runtime::new(self.host_uuid).await
|
||||
}
|
||||
|
||||
pub async fn power_management_context(&self) -> Result<PowerManagementContext> {
|
||||
let context = RuntimeContext::new(self.host_uuid).await?;
|
||||
Ok(PowerManagementContext { context })
|
||||
}
|
||||
}
|
||||
|
167
crates/runtime/src/power.rs
Normal file
167
crates/runtime/src/power.rs
Normal file
@ -0,0 +1,167 @@
|
||||
use anyhow::Result;
|
||||
use indexmap::IndexMap;
|
||||
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<CpuTopologyInfo> {
|
||||
let mut cores: IndexMap<(u32, u32, u32), Vec<CpuTopologyInfo>> = IndexMap::new();
|
||||
let mut pe_cores = false;
|
||||
let mut last: Option<SysctlCputopo> = 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::<Vec<_>>()
|
||||
}
|
||||
|
||||
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<Vec<CpuTopologyInfo>> {
|
||||
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?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set scheduler policy name.
|
||||
pub async fn set_scheduler_policy(&self, policy: impl AsRef<str>) -> Result<()> {
|
||||
self.context
|
||||
.xen
|
||||
.call
|
||||
.set_cpufreq_gov(CpuId::All, policy)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "krata-xencall"
|
||||
description = "An implementation of direct interfacing to xen privcmd for krata."
|
||||
description = "An implementation of direct interfacing to Xen privcmd for krata"
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
homepage.workspace = true
|
||||
@ -35,5 +35,5 @@ name = "xencall-version-capabilities"
|
||||
path = "examples/version_capabilities.rs"
|
||||
|
||||
[[example]]
|
||||
name = "xencall-vcpu-context"
|
||||
path = "examples/vcpu_context.rs"
|
||||
name = "xencall-power-management"
|
||||
path = "examples/power_management.rs"
|
||||
|
19
crates/xen/xencall/examples/cputopo.rs
Normal file
19
crates/xen/xencall/examples/cputopo.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use xencall::error::Result;
|
||||
use xencall::sys::CpuId;
|
||||
use xencall::XenCall;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
env_logger::init();
|
||||
|
||||
let call = XenCall::open(0)?;
|
||||
let physinfo = call.phys_info().await?;
|
||||
println!("{:?}", physinfo);
|
||||
let topology = call.cpu_topology().await?;
|
||||
println!("{:?}", topology);
|
||||
call.set_cpufreq_gov(CpuId::All, "performance").await?;
|
||||
call.set_cpufreq_gov(CpuId::Single(0), "performance")
|
||||
.await?;
|
||||
call.set_turbo_mode(CpuId::All, true).await?;
|
||||
Ok(())
|
||||
}
|
19
crates/xen/xencall/examples/power_management.rs
Normal file
19
crates/xen/xencall/examples/power_management.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use xencall::error::Result;
|
||||
use xencall::sys::CpuId;
|
||||
use xencall::XenCall;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
env_logger::init();
|
||||
|
||||
let call = XenCall::open(0)?;
|
||||
let physinfo = call.phys_info().await?;
|
||||
println!("{:?}", physinfo);
|
||||
let topology = call.cpu_topology().await?;
|
||||
println!("{:?}", topology);
|
||||
call.set_cpufreq_gov(CpuId::All, "performance").await?;
|
||||
call.set_cpufreq_gov(CpuId::Single(0), "performance")
|
||||
.await?;
|
||||
call.set_turbo_mode(CpuId::All, true).await?;
|
||||
Ok(())
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
use xencall::error::Result;
|
||||
use xencall::XenCall;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
env_logger::init();
|
||||
|
||||
let call = XenCall::open(0)?;
|
||||
let context = call.get_vcpu_context(224, 0).await?;
|
||||
println!("{:?}", context);
|
||||
Ok(())
|
||||
}
|
@ -14,6 +14,8 @@ pub enum Error {
|
||||
PopulatePhysmapFailed,
|
||||
#[error("mmap batch failed: {0}")]
|
||||
MmapBatchFailed(nix::errno::Errno),
|
||||
#[error("specified value is too long")]
|
||||
ValueTooLong,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
@ -3,28 +3,42 @@ pub mod sys;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::sys::{
|
||||
AddressSize, CreateDomain, DomCtl, DomCtlValue, DomCtlVcpuContext, EvtChnAllocUnbound,
|
||||
GetDomainInfo, GetPageFrameInfo3, Hypercall, HypercallInit, MaxMem, MaxVcpus, MemoryMap,
|
||||
MemoryReservation, MmapBatch, MmapResource, MmuExtOp, MultiCallEntry, VcpuGuestContext,
|
||||
VcpuGuestContextAny, XenCapabilitiesInfo, HYPERVISOR_DOMCTL, HYPERVISOR_EVENT_CHANNEL_OP,
|
||||
HYPERVISOR_MEMORY_OP, HYPERVISOR_MMUEXT_OP, HYPERVISOR_MULTICALL, HYPERVISOR_XEN_VERSION,
|
||||
XENVER_CAPABILITIES, XEN_DOMCTL_CREATEDOMAIN, XEN_DOMCTL_DESTROYDOMAIN,
|
||||
XEN_DOMCTL_GETDOMAININFO, XEN_DOMCTL_GETPAGEFRAMEINFO3, XEN_DOMCTL_GETVCPUCONTEXT,
|
||||
XEN_DOMCTL_HYPERCALL_INIT, XEN_DOMCTL_MAX_MEM, XEN_DOMCTL_MAX_VCPUS, XEN_DOMCTL_PAUSEDOMAIN,
|
||||
XEN_DOMCTL_SETVCPUCONTEXT, XEN_DOMCTL_SET_ADDRESS_SIZE, XEN_DOMCTL_UNPAUSEDOMAIN,
|
||||
XEN_MEM_CLAIM_PAGES, XEN_MEM_MEMORY_MAP, XEN_MEM_POPULATE_PHYSMAP,
|
||||
AddToPhysmap, AddressSize, AssignDevice, CreateDomain, DomCtl, DomCtlValue, DomCtlVcpuContext,
|
||||
EvtChnAllocUnbound, GetDomainInfo, GetPageFrameInfo3, HvmContext, HvmParam, Hypercall,
|
||||
HypercallInit, IoMemPermission, IoPortPermission, IrqPermission, MaxMem, MaxVcpus, MemoryMap,
|
||||
MemoryReservation, MmapBatch, MmapResource, MmuExtOp, MultiCallEntry, PagingMempool,
|
||||
PciAssignDevice, XenCapabilitiesInfo, DOMCTL_DEV_PCI, HYPERVISOR_DOMCTL,
|
||||
HYPERVISOR_EVENT_CHANNEL_OP, HYPERVISOR_HVM_OP, HYPERVISOR_MEMORY_OP, HYPERVISOR_MMUEXT_OP,
|
||||
HYPERVISOR_MULTICALL, HYPERVISOR_XEN_VERSION, XENVER_CAPABILITIES, XEN_DOMCTL_ASSIGN_DEVICE,
|
||||
XEN_DOMCTL_CREATEDOMAIN, XEN_DOMCTL_DESTROYDOMAIN, XEN_DOMCTL_GETDOMAININFO,
|
||||
XEN_DOMCTL_GETHVMCONTEXT, XEN_DOMCTL_GETPAGEFRAMEINFO3, XEN_DOMCTL_HYPERCALL_INIT,
|
||||
XEN_DOMCTL_IOMEM_PERMISSION, XEN_DOMCTL_IOPORT_PERMISSION, XEN_DOMCTL_IRQ_PERMISSION,
|
||||
XEN_DOMCTL_MAX_MEM, XEN_DOMCTL_MAX_VCPUS, XEN_DOMCTL_PAUSEDOMAIN, XEN_DOMCTL_SETHVMCONTEXT,
|
||||
XEN_DOMCTL_SETVCPUCONTEXT, XEN_DOMCTL_SET_ADDRESS_SIZE, XEN_DOMCTL_SET_PAGING_MEMPOOL_SIZE,
|
||||
XEN_DOMCTL_UNPAUSEDOMAIN, XEN_MEM_ADD_TO_PHYSMAP, XEN_MEM_CLAIM_PAGES, XEN_MEM_MEMORY_MAP,
|
||||
XEN_MEM_POPULATE_PHYSMAP,
|
||||
};
|
||||
use libc::{c_int, mmap, usleep, MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE};
|
||||
use libc::{c_int, mmap, MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE};
|
||||
use log::trace;
|
||||
use nix::errno::Errno;
|
||||
use std::ffi::{c_long, c_uint, c_ulong, c_void};
|
||||
use std::sync::Arc;
|
||||
use sys::{XEN_DOMCTL_MAX_INTERFACE_VERSION, XEN_DOMCTL_MIN_INTERFACE_VERSION};
|
||||
use std::time::Duration;
|
||||
use sys::{
|
||||
CpuId, E820Entry, ForeignMemoryMap, PhysdevMapPirq, Sysctl, SysctlCputopo, SysctlCputopoinfo,
|
||||
SysctlPhysinfo, SysctlPmOp, SysctlPmOpValue, SysctlSetCpuFreqGov, SysctlValue,
|
||||
VcpuGuestContextAny, HYPERVISOR_PHYSDEV_OP, HYPERVISOR_SYSCTL, PHYSDEVOP_MAP_PIRQ,
|
||||
XEN_DOMCTL_MAX_INTERFACE_VERSION, XEN_DOMCTL_MIN_INTERFACE_VERSION, XEN_MEM_SET_MEMORY_MAP,
|
||||
XEN_SYSCTL_CPUTOPOINFO, XEN_SYSCTL_MAX_INTERFACE_VERSION, XEN_SYSCTL_MIN_INTERFACE_VERSION,
|
||||
XEN_SYSCTL_PHYSINFO, XEN_SYSCTL_PM_OP, XEN_SYSCTL_PM_OP_DISABLE_TURBO,
|
||||
XEN_SYSCTL_PM_OP_ENABLE_TURBO,
|
||||
};
|
||||
use tokio::sync::Semaphore;
|
||||
use tokio::time::sleep;
|
||||
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::ptr::addr_of_mut;
|
||||
use std::ptr::{addr_of_mut, null_mut};
|
||||
use std::slice;
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -32,6 +46,7 @@ pub struct XenCall {
|
||||
pub handle: Arc<File>,
|
||||
semaphore: Arc<Semaphore>,
|
||||
domctl_interface_version: u32,
|
||||
sysctl_interface_version: u32,
|
||||
}
|
||||
|
||||
impl XenCall {
|
||||
@ -42,10 +57,12 @@ impl XenCall {
|
||||
.open("/dev/xen/privcmd")?;
|
||||
let domctl_interface_version =
|
||||
XenCall::detect_domctl_interface_version(&handle, current_domid)?;
|
||||
let sysctl_interface_version = XenCall::detect_sysctl_interface_version(&handle)?;
|
||||
Ok(XenCall {
|
||||
handle: Arc::new(handle),
|
||||
semaphore: Arc::new(Semaphore::new(1)),
|
||||
domctl_interface_version,
|
||||
sysctl_interface_version,
|
||||
})
|
||||
}
|
||||
|
||||
@ -73,6 +90,32 @@ impl XenCall {
|
||||
Err(Error::XenVersionUnsupported)
|
||||
}
|
||||
|
||||
fn detect_sysctl_interface_version(handle: &File) -> Result<u32> {
|
||||
for version in XEN_SYSCTL_MIN_INTERFACE_VERSION..XEN_SYSCTL_MAX_INTERFACE_VERSION + 1 {
|
||||
let mut sysctl = Sysctl {
|
||||
cmd: XEN_SYSCTL_CPUTOPOINFO,
|
||||
interface_version: version,
|
||||
value: SysctlValue {
|
||||
cputopoinfo: SysctlCputopoinfo {
|
||||
num_cpus: 0,
|
||||
handle: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
unsafe {
|
||||
let mut call = Hypercall {
|
||||
op: HYPERVISOR_SYSCTL,
|
||||
arg: [addr_of_mut!(sysctl) as u64, 0, 0, 0, 0],
|
||||
};
|
||||
let result = sys::hypercall(handle.as_raw_fd(), &mut call).unwrap_or(-1);
|
||||
if result == 0 {
|
||||
return Ok(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(Error::XenVersionUnsupported)
|
||||
}
|
||||
|
||||
pub async fn mmap(&self, addr: u64, len: u64) -> Option<u64> {
|
||||
let _permit = self.semaphore.acquire().await.ok()?;
|
||||
trace!(
|
||||
@ -227,8 +270,8 @@ impl XenCall {
|
||||
num: num as u32,
|
||||
domid: domid as u16,
|
||||
addr,
|
||||
mfns: mfns.as_mut_ptr(),
|
||||
errors: errors.as_mut_ptr(),
|
||||
mfns: mfns.as_mut_ptr() as u64,
|
||||
errors: errors.as_mut_ptr() as u64,
|
||||
};
|
||||
|
||||
let result = sys::mmapbatch(self.handle.as_raw_fd(), &mut batch);
|
||||
@ -237,7 +280,7 @@ impl XenCall {
|
||||
return Err(Error::MmapBatchFailed(errno))?;
|
||||
}
|
||||
|
||||
usleep(100);
|
||||
sleep(Duration::from_micros(100)).await;
|
||||
|
||||
let mut i: usize = 0;
|
||||
let mut paged: usize = 0;
|
||||
@ -252,8 +295,8 @@ impl XenCall {
|
||||
num: 1,
|
||||
domid: domid as u16,
|
||||
addr: addr + ((i as u64) << 12),
|
||||
mfns: mfns.as_mut_ptr().add(i),
|
||||
errors: errors.as_mut_ptr().add(i),
|
||||
mfns: mfns.as_mut_ptr().add(i) as u64,
|
||||
errors: errors.as_mut_ptr().add(i) as u64,
|
||||
};
|
||||
|
||||
loop {
|
||||
@ -453,45 +496,19 @@ impl XenCall {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_vcpu_context(&self, domid: u32, vcpu: u32) -> Result<VcpuGuestContext> {
|
||||
trace!(
|
||||
"domctl fd={} get_vcpu_context domid={}",
|
||||
self.handle.as_raw_fd(),
|
||||
domid,
|
||||
);
|
||||
let mut wrapper = VcpuGuestContextAny {
|
||||
value: VcpuGuestContext::default(),
|
||||
};
|
||||
let mut domctl = DomCtl {
|
||||
cmd: XEN_DOMCTL_GETVCPUCONTEXT,
|
||||
interface_version: self.domctl_interface_version,
|
||||
domid,
|
||||
value: DomCtlValue {
|
||||
vcpu_context: DomCtlVcpuContext {
|
||||
vcpu,
|
||||
ctx: addr_of_mut!(wrapper) as c_ulong,
|
||||
},
|
||||
},
|
||||
};
|
||||
self.hypercall1(HYPERVISOR_DOMCTL, addr_of_mut!(domctl) as c_ulong)
|
||||
.await?;
|
||||
Ok(unsafe { wrapper.value })
|
||||
}
|
||||
|
||||
pub async fn set_vcpu_context(
|
||||
&self,
|
||||
domid: u32,
|
||||
vcpu: u32,
|
||||
context: &VcpuGuestContext,
|
||||
mut context: VcpuGuestContextAny,
|
||||
) -> Result<()> {
|
||||
trace!(
|
||||
"domctl fd={} set_vcpu_context domid={} context={:?}",
|
||||
self.handle.as_raw_fd(),
|
||||
domid,
|
||||
context,
|
||||
unsafe { context.value }
|
||||
);
|
||||
|
||||
let mut value = VcpuGuestContextAny { value: *context };
|
||||
let mut domctl = DomCtl {
|
||||
cmd: XEN_DOMCTL_SETVCPUCONTEXT,
|
||||
interface_version: self.domctl_interface_version,
|
||||
@ -499,7 +516,7 @@ impl XenCall {
|
||||
value: DomCtlValue {
|
||||
vcpu_context: DomCtlVcpuContext {
|
||||
vcpu,
|
||||
ctx: addr_of_mut!(value) as c_ulong,
|
||||
ctx: addr_of_mut!(context) as c_ulong,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -569,26 +586,48 @@ impl XenCall {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_memory_map(&self, size_of_entry: usize) -> Result<Vec<u8>> {
|
||||
pub async fn get_memory_map(&self, max_entries: u32) -> Result<Vec<E820Entry>> {
|
||||
let mut memory_map = MemoryMap {
|
||||
count: 0,
|
||||
count: max_entries,
|
||||
buffer: 0,
|
||||
};
|
||||
let mut entries = vec![E820Entry::default(); max_entries as usize];
|
||||
memory_map.buffer = entries.as_mut_ptr() as c_ulong;
|
||||
self.hypercall2(
|
||||
HYPERVISOR_MEMORY_OP,
|
||||
XEN_MEM_MEMORY_MAP as c_ulong,
|
||||
addr_of_mut!(memory_map) as c_ulong,
|
||||
)
|
||||
.await?;
|
||||
entries.truncate(memory_map.count as usize);
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
pub async fn set_memory_map(
|
||||
&self,
|
||||
domid: u32,
|
||||
entries: Vec<E820Entry>,
|
||||
) -> Result<Vec<E820Entry>> {
|
||||
trace!(
|
||||
"fd={} set_memory_map domid={} entries={:?}",
|
||||
self.handle.as_raw_fd(),
|
||||
domid,
|
||||
entries
|
||||
);
|
||||
let mut memory_map = ForeignMemoryMap {
|
||||
domid: domid as u16,
|
||||
map: MemoryMap {
|
||||
count: entries.len() as u32,
|
||||
buffer: entries.as_ptr() as u64,
|
||||
},
|
||||
};
|
||||
self.hypercall2(
|
||||
HYPERVISOR_MEMORY_OP,
|
||||
XEN_MEM_MEMORY_MAP as c_ulong,
|
||||
XEN_MEM_SET_MEMORY_MAP as c_ulong,
|
||||
addr_of_mut!(memory_map) as c_ulong,
|
||||
)
|
||||
.await?;
|
||||
let mut buffer = vec![0u8; memory_map.count as usize * size_of_entry];
|
||||
memory_map.buffer = buffer.as_mut_ptr() as c_ulong;
|
||||
self.hypercall2(
|
||||
HYPERVISOR_MEMORY_OP,
|
||||
XEN_MEM_MEMORY_MAP as c_ulong,
|
||||
addr_of_mut!(memory_map) as c_ulong,
|
||||
)
|
||||
.await?;
|
||||
Ok(buffer)
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
pub async fn populate_physmap(
|
||||
@ -611,24 +650,14 @@ impl XenCall {
|
||||
domid: domid as u16,
|
||||
};
|
||||
|
||||
let calls = &mut [MultiCallEntry {
|
||||
op: HYPERVISOR_MEMORY_OP,
|
||||
result: 0,
|
||||
args: [
|
||||
let code = self
|
||||
.hypercall2(
|
||||
HYPERVISOR_MEMORY_OP,
|
||||
XEN_MEM_POPULATE_PHYSMAP as c_ulong,
|
||||
addr_of_mut!(reservation) as c_ulong,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
}];
|
||||
self.multicall(calls).await?;
|
||||
let code = calls[0].result;
|
||||
if code > !0xfff {
|
||||
return Err(Error::PopulatePhysmapFailed);
|
||||
}
|
||||
if code as usize > extent_starts.len() {
|
||||
)
|
||||
.await?;
|
||||
if code as usize != extent_starts.len() {
|
||||
return Err(Error::PopulatePhysmapFailed);
|
||||
}
|
||||
let extents = extent_starts[0..code as usize].to_vec();
|
||||
@ -658,6 +687,31 @@ impl XenCall {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_to_physmap(&self, domid: u32, space: u32, idx: u64, pfn: u64) -> Result<()> {
|
||||
trace!(
|
||||
"memory fd={} add_to_physmap domid={} space={} idx={} pfn={}",
|
||||
self.handle.as_raw_fd(),
|
||||
domid,
|
||||
space,
|
||||
idx,
|
||||
pfn,
|
||||
);
|
||||
let mut add = AddToPhysmap {
|
||||
domid: domid as u16,
|
||||
size: 0,
|
||||
space,
|
||||
idx,
|
||||
gpfn: pfn,
|
||||
};
|
||||
self.hypercall2(
|
||||
HYPERVISOR_MEMORY_OP,
|
||||
XEN_MEM_ADD_TO_PHYSMAP as c_ulong,
|
||||
addr_of_mut!(add) as c_ulong,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn mmuext(&self, domid: u32, cmd: c_uint, arg1: u64, arg2: u64) -> Result<()> {
|
||||
let mut ops = MmuExtOp { cmd, arg1, arg2 };
|
||||
|
||||
@ -671,4 +725,366 @@ impl XenCall {
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub async fn iomem_permission(
|
||||
&self,
|
||||
domid: u32,
|
||||
first_mfn: u64,
|
||||
nr_mfns: u64,
|
||||
allow: bool,
|
||||
) -> Result<()> {
|
||||
trace!(
|
||||
"domctl fd={} iomem_permission domid={} first_mfn={:#x}, nr_mfns={:#x} allow={}",
|
||||
self.handle.as_raw_fd(),
|
||||
domid,
|
||||
first_mfn,
|
||||
nr_mfns,
|
||||
allow,
|
||||
);
|
||||
let mut domctl = DomCtl {
|
||||
cmd: XEN_DOMCTL_IOMEM_PERMISSION,
|
||||
interface_version: self.domctl_interface_version,
|
||||
domid,
|
||||
value: DomCtlValue {
|
||||
iomem_permission: IoMemPermission {
|
||||
first_mfn,
|
||||
nr_mfns,
|
||||
allow: if allow { 1 } else { 0 },
|
||||
},
|
||||
},
|
||||
};
|
||||
self.hypercall1(HYPERVISOR_DOMCTL, addr_of_mut!(domctl) as c_ulong)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn ioport_permission(
|
||||
&self,
|
||||
domid: u32,
|
||||
first_port: u32,
|
||||
nr_ports: u32,
|
||||
allow: bool,
|
||||
) -> Result<()> {
|
||||
trace!(
|
||||
"domctl fd={} ioport_permission domid={} first_port={:#x}, nr_ports={:#x} allow={}",
|
||||
self.handle.as_raw_fd(),
|
||||
domid,
|
||||
first_port,
|
||||
nr_ports,
|
||||
allow,
|
||||
);
|
||||
let mut domctl = DomCtl {
|
||||
cmd: XEN_DOMCTL_IOPORT_PERMISSION,
|
||||
interface_version: self.domctl_interface_version,
|
||||
domid,
|
||||
value: DomCtlValue {
|
||||
ioport_permission: IoPortPermission {
|
||||
first_port,
|
||||
nr_ports,
|
||||
allow: if allow { 1 } else { 0 },
|
||||
},
|
||||
},
|
||||
};
|
||||
self.hypercall1(HYPERVISOR_DOMCTL, addr_of_mut!(domctl) as c_ulong)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn irq_permission(&self, domid: u32, irq: u32, allow: bool) -> Result<()> {
|
||||
trace!(
|
||||
"domctl fd={} irq_permission domid={} irq={} allow={}",
|
||||
self.handle.as_raw_fd(),
|
||||
domid,
|
||||
irq,
|
||||
allow,
|
||||
);
|
||||
let mut domctl = DomCtl {
|
||||
cmd: XEN_DOMCTL_IRQ_PERMISSION,
|
||||
interface_version: self.domctl_interface_version,
|
||||
domid,
|
||||
value: DomCtlValue {
|
||||
irq_permission: IrqPermission {
|
||||
pirq: irq,
|
||||
allow: if allow { 1 } else { 0 },
|
||||
pad: [0; 3],
|
||||
},
|
||||
},
|
||||
};
|
||||
self.hypercall1(HYPERVISOR_DOMCTL, addr_of_mut!(domctl) as c_ulong)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::field_reassign_with_default)]
|
||||
pub async fn map_pirq(&self, domid: u32, index: isize, pirq: Option<u32>) -> Result<u32> {
|
||||
trace!(
|
||||
"physdev fd={} map_pirq domid={} index={} pirq={:?}",
|
||||
self.handle.as_raw_fd(),
|
||||
domid,
|
||||
index,
|
||||
pirq,
|
||||
);
|
||||
let mut physdev = PhysdevMapPirq {
|
||||
domid: domid as u16,
|
||||
typ: 0x1,
|
||||
index: index as c_int,
|
||||
pirq: pirq.map(|x| x as c_int).unwrap_or(index as c_int),
|
||||
..Default::default()
|
||||
};
|
||||
physdev.domid = domid as u16;
|
||||
physdev.typ = 0x1;
|
||||
physdev.index = index as c_int;
|
||||
physdev.pirq = pirq.map(|x| x as c_int).unwrap_or(index as c_int);
|
||||
self.hypercall2(
|
||||
HYPERVISOR_PHYSDEV_OP,
|
||||
PHYSDEVOP_MAP_PIRQ,
|
||||
addr_of_mut!(physdev) as c_ulong,
|
||||
)
|
||||
.await?;
|
||||
Ok(physdev.pirq as u32)
|
||||
}
|
||||
|
||||
pub async fn assign_device(&self, domid: u32, sbdf: u32, flags: u32) -> Result<()> {
|
||||
trace!(
|
||||
"domctl fd={} assign_device domid={} sbdf={} flags={}",
|
||||
self.handle.as_raw_fd(),
|
||||
domid,
|
||||
sbdf,
|
||||
flags,
|
||||
);
|
||||
let mut domctl = DomCtl {
|
||||
cmd: XEN_DOMCTL_ASSIGN_DEVICE,
|
||||
interface_version: self.domctl_interface_version,
|
||||
domid,
|
||||
value: DomCtlValue {
|
||||
assign_device: AssignDevice {
|
||||
device: DOMCTL_DEV_PCI,
|
||||
flags,
|
||||
pci_assign_device: PciAssignDevice { sbdf, padding: 0 },
|
||||
},
|
||||
},
|
||||
};
|
||||
self.hypercall1(HYPERVISOR_DOMCTL, addr_of_mut!(domctl) as c_ulong)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::field_reassign_with_default)]
|
||||
pub async fn set_hvm_param(&self, domid: u32, index: u32, value: u64) -> Result<()> {
|
||||
trace!(
|
||||
"set_hvm_param fd={} domid={} index={} value={:?}",
|
||||
self.handle.as_raw_fd(),
|
||||
domid,
|
||||
index,
|
||||
value,
|
||||
);
|
||||
let mut param = HvmParam::default();
|
||||
param.domid = domid as u16;
|
||||
param.index = index;
|
||||
param.value = value;
|
||||
self.hypercall2(HYPERVISOR_HVM_OP, 0, addr_of_mut!(param) as c_ulong)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_hvm_context(&self, domid: u32, buffer: Option<&mut [u8]>) -> Result<u32> {
|
||||
trace!(
|
||||
"domctl fd={} get_hvm_context domid={}",
|
||||
self.handle.as_raw_fd(),
|
||||
domid,
|
||||
);
|
||||
let mut domctl = DomCtl {
|
||||
cmd: XEN_DOMCTL_GETHVMCONTEXT,
|
||||
interface_version: self.domctl_interface_version,
|
||||
domid,
|
||||
value: DomCtlValue {
|
||||
hvm_context: HvmContext {
|
||||
size: buffer.as_ref().map(|x| x.len()).unwrap_or(0) as u32,
|
||||
buffer: buffer.map(|x| x.as_mut_ptr()).unwrap_or(null_mut()) as u64,
|
||||
},
|
||||
},
|
||||
};
|
||||
self.hypercall1(HYPERVISOR_DOMCTL, addr_of_mut!(domctl) as c_ulong)
|
||||
.await?;
|
||||
Ok(unsafe { domctl.value.hvm_context.size })
|
||||
}
|
||||
|
||||
pub async fn set_hvm_context(&self, domid: u32, buffer: &mut [u8]) -> Result<u32> {
|
||||
trace!(
|
||||
"domctl fd={} set_hvm_context domid={}",
|
||||
self.handle.as_raw_fd(),
|
||||
domid,
|
||||
);
|
||||
let mut domctl = DomCtl {
|
||||
cmd: XEN_DOMCTL_SETHVMCONTEXT,
|
||||
interface_version: self.domctl_interface_version,
|
||||
domid,
|
||||
value: DomCtlValue {
|
||||
hvm_context: HvmContext {
|
||||
size: buffer.len() as u32,
|
||||
buffer: buffer.as_ptr() as u64,
|
||||
},
|
||||
},
|
||||
};
|
||||
self.hypercall1(HYPERVISOR_DOMCTL, addr_of_mut!(domctl) as c_ulong)
|
||||
.await?;
|
||||
Ok(unsafe { domctl.value.hvm_context.size })
|
||||
}
|
||||
|
||||
pub async fn set_paging_mempool_size(&self, domid: u32, size: u64) -> Result<()> {
|
||||
trace!(
|
||||
"domctl fd={} set_paging_mempool_size domid={} size={}",
|
||||
self.handle.as_raw_fd(),
|
||||
domid,
|
||||
size,
|
||||
);
|
||||
let mut domctl = DomCtl {
|
||||
cmd: XEN_DOMCTL_SET_PAGING_MEMPOOL_SIZE,
|
||||
interface_version: self.domctl_interface_version,
|
||||
domid,
|
||||
value: DomCtlValue {
|
||||
paging_mempool: PagingMempool { size },
|
||||
},
|
||||
};
|
||||
self.hypercall1(HYPERVISOR_DOMCTL, addr_of_mut!(domctl) as c_ulong)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn cpu_topology(&self) -> Result<Vec<SysctlCputopo>> {
|
||||
let mut sysctl = Sysctl {
|
||||
cmd: XEN_SYSCTL_CPUTOPOINFO,
|
||||
interface_version: self.sysctl_interface_version,
|
||||
value: SysctlValue {
|
||||
cputopoinfo: SysctlCputopoinfo {
|
||||
num_cpus: 0,
|
||||
handle: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
self.hypercall1(HYPERVISOR_SYSCTL, addr_of_mut!(sysctl) as c_ulong)
|
||||
.await?;
|
||||
let cpus = unsafe { sysctl.value.cputopoinfo.num_cpus };
|
||||
let mut topos = vec![
|
||||
SysctlCputopo {
|
||||
core: 0,
|
||||
socket: 0,
|
||||
node: 0
|
||||
};
|
||||
cpus as usize
|
||||
];
|
||||
let mut sysctl = Sysctl {
|
||||
cmd: XEN_SYSCTL_CPUTOPOINFO,
|
||||
interface_version: self.sysctl_interface_version,
|
||||
value: SysctlValue {
|
||||
cputopoinfo: SysctlCputopoinfo {
|
||||
num_cpus: cpus,
|
||||
handle: topos.as_mut_ptr() as c_ulong,
|
||||
},
|
||||
},
|
||||
};
|
||||
self.hypercall1(HYPERVISOR_SYSCTL, addr_of_mut!(sysctl) as c_ulong)
|
||||
.await?;
|
||||
Ok(topos)
|
||||
}
|
||||
|
||||
pub async fn phys_info(&self) -> Result<SysctlPhysinfo> {
|
||||
let mut sysctl = Sysctl {
|
||||
cmd: XEN_SYSCTL_PHYSINFO,
|
||||
interface_version: self.sysctl_interface_version,
|
||||
value: SysctlValue {
|
||||
phys_info: SysctlPhysinfo::default(),
|
||||
},
|
||||
};
|
||||
self.hypercall1(HYPERVISOR_SYSCTL, addr_of_mut!(sysctl) as c_ulong)
|
||||
.await?;
|
||||
Ok(unsafe { sysctl.value.phys_info })
|
||||
}
|
||||
|
||||
pub async fn set_cpufreq_gov(&self, cpuid: CpuId, gov: impl AsRef<str>) -> Result<()> {
|
||||
match cpuid {
|
||||
CpuId::All => {
|
||||
let phys_info = self.phys_info().await?;
|
||||
for cpuid in 0..phys_info.max_cpu_id + 1 {
|
||||
self.do_set_cpufreq_gov(cpuid, gov.as_ref()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
CpuId::Single(id) => {
|
||||
self.do_set_cpufreq_gov(id, gov).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn do_set_cpufreq_gov(&self, cpuid: u32, gov: impl AsRef<str>) -> Result<()> {
|
||||
let governor = gov.as_ref().as_bytes().to_vec();
|
||||
if governor.len() > 15 {
|
||||
return Err(Error::ValueTooLong);
|
||||
}
|
||||
|
||||
let mut scaling_governor = [0u8; 16];
|
||||
|
||||
// leave space for the last byte to be zero at all times.
|
||||
for i in 0..15usize {
|
||||
if i >= governor.len() {
|
||||
break;
|
||||
}
|
||||
scaling_governor[i] = governor[i];
|
||||
}
|
||||
|
||||
let mut sysctl = Sysctl {
|
||||
cmd: XEN_SYSCTL_PM_OP,
|
||||
interface_version: self.sysctl_interface_version,
|
||||
value: SysctlValue {
|
||||
pm_op: SysctlPmOp {
|
||||
cmd: XEN_SYSCTL_PM_OP_ENABLE_TURBO,
|
||||
cpuid,
|
||||
value: SysctlPmOpValue {
|
||||
set_gov: SysctlSetCpuFreqGov { scaling_governor },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
self.hypercall1(HYPERVISOR_SYSCTL, addr_of_mut!(sysctl) as c_ulong)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_turbo_mode(&self, cpuid: CpuId, enable: bool) -> Result<()> {
|
||||
match cpuid {
|
||||
CpuId::All => {
|
||||
let phys_info = self.phys_info().await?;
|
||||
for cpuid in 0..phys_info.max_cpu_id + 1 {
|
||||
self.do_set_turbo_mode(cpuid, enable).await?;
|
||||
}
|
||||
}
|
||||
|
||||
CpuId::Single(id) => {
|
||||
self.do_set_turbo_mode(id, enable).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn do_set_turbo_mode(&self, cpuid: u32, enable: bool) -> Result<()> {
|
||||
let mut sysctl = Sysctl {
|
||||
cmd: XEN_SYSCTL_PM_OP,
|
||||
interface_version: self.sysctl_interface_version,
|
||||
value: SysctlValue {
|
||||
pm_op: SysctlPmOp {
|
||||
cmd: if enable {
|
||||
XEN_SYSCTL_PM_OP_ENABLE_TURBO
|
||||
} else {
|
||||
XEN_SYSCTL_PM_OP_DISABLE_TURBO
|
||||
},
|
||||
cpuid,
|
||||
value: SysctlPmOpValue { pad: [0u8; 128] },
|
||||
},
|
||||
},
|
||||
};
|
||||
self.hypercall1(HYPERVISOR_SYSCTL, addr_of_mut!(sysctl) as c_ulong)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
/// Handwritten hypercall bindings.
|
||||
use nix::ioctl_readwrite_bad;
|
||||
use std::ffi::{c_char, c_int, c_uint, c_ulong};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
@ -35,8 +34,8 @@ pub struct MmapBatch {
|
||||
pub num: u32,
|
||||
pub domid: u16,
|
||||
pub addr: u64,
|
||||
pub mfns: *mut u64,
|
||||
pub errors: *mut c_int,
|
||||
pub mfns: u64,
|
||||
pub errors: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
@ -104,6 +103,7 @@ pub const XEN_DOMCTL_CDF_HAP: u32 = 1u32 << 1;
|
||||
pub const XEN_DOMCTL_CDF_S3_INTEGRITY: u32 = 1u32 << 2;
|
||||
pub const XEN_DOMCTL_CDF_OOS_OFF: u32 = 1u32 << 3;
|
||||
pub const XEN_DOMCTL_CDF_XS_DOMAIN: u32 = 1u32 << 4;
|
||||
pub const XEN_DOMCTL_CDF_IOMMU: u32 = 1u32 << 5;
|
||||
|
||||
pub const XEN_X86_EMU_LAPIC: u32 = 1 << 0;
|
||||
pub const XEN_X86_EMU_HPET: u32 = 1 << 1;
|
||||
@ -199,6 +199,7 @@ pub const XEN_DOMCTL_PSR_CAT_OP: u32 = 78;
|
||||
pub const XEN_DOMCTL_SOFT_RESET: u32 = 79;
|
||||
pub const XEN_DOMCTL_SET_GNTTAB_LIMITS: u32 = 80;
|
||||
pub const XEN_DOMCTL_VUART_OP: u32 = 81;
|
||||
pub const XEN_DOMCTL_SET_PAGING_MEMPOOL_SIZE: u32 = 86;
|
||||
pub const XEN_DOMCTL_GDBSX_GUESTMEMIO: u32 = 1000;
|
||||
pub const XEN_DOMCTL_GDBSX_PAUSEVCPU: u32 = 1001;
|
||||
pub const XEN_DOMCTL_GDBSX_UNPAUSEVCPU: u32 = 1002;
|
||||
@ -237,6 +238,12 @@ pub union DomCtlValue {
|
||||
pub vcpu_context: DomCtlVcpuContext,
|
||||
pub address_size: AddressSize,
|
||||
pub get_page_frame_info: GetPageFrameInfo3,
|
||||
pub ioport_permission: IoPortPermission,
|
||||
pub iomem_permission: IoMemPermission,
|
||||
pub irq_permission: IrqPermission,
|
||||
pub assign_device: AssignDevice,
|
||||
pub hvm_context: HvmContext,
|
||||
pub paging_mempool: PagingMempool,
|
||||
pub pad: [u8; 128],
|
||||
}
|
||||
|
||||
@ -261,11 +268,8 @@ impl Default for CreateDomain {
|
||||
fn default() -> Self {
|
||||
CreateDomain {
|
||||
ssidref: SECINITSID_DOMU,
|
||||
handle: Uuid::new_v4().into_bytes(),
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
handle: [0; 16],
|
||||
flags: 0,
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
flags: 1 << XEN_DOMCTL_CDF_HVM_GUEST,
|
||||
iommu_opts: 0,
|
||||
max_vcpus: 1,
|
||||
max_evtchn_port: 1023,
|
||||
@ -309,6 +313,30 @@ pub struct GetPageFrameInfo3 {
|
||||
pub array: c_ulong,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct IoPortPermission {
|
||||
pub first_port: u32,
|
||||
pub nr_ports: u32,
|
||||
pub allow: u8,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct IoMemPermission {
|
||||
pub first_mfn: u64,
|
||||
pub nr_mfns: u64,
|
||||
pub allow: u8,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct IrqPermission {
|
||||
pub pirq: u32,
|
||||
pub allow: u8,
|
||||
pub pad: [u8; 3],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@ -317,6 +345,8 @@ pub struct ArchDomainConfig {
|
||||
pub misc_flags: u32,
|
||||
}
|
||||
|
||||
pub const X86_EMU_LAPIC: u32 = 1 << 0;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
@ -369,6 +399,16 @@ pub struct MemoryReservation {
|
||||
pub domid: u16,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct AddToPhysmap {
|
||||
pub domid: u16,
|
||||
pub size: u16,
|
||||
pub space: u32,
|
||||
pub idx: u64,
|
||||
pub gpfn: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct MultiCallEntry {
|
||||
@ -378,8 +418,10 @@ pub struct MultiCallEntry {
|
||||
}
|
||||
|
||||
pub const XEN_MEM_POPULATE_PHYSMAP: u32 = 6;
|
||||
pub const XEN_MEM_MEMORY_MAP: u32 = 9;
|
||||
pub const XEN_MEM_MEMORY_MAP: u32 = 10;
|
||||
pub const XEN_MEM_SET_MEMORY_MAP: u32 = 13;
|
||||
pub const XEN_MEM_CLAIM_PAGES: u32 = 24;
|
||||
pub const XEN_MEM_ADD_TO_PHYSMAP: u32 = 7;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
@ -388,6 +430,13 @@ pub struct MemoryMap {
|
||||
pub buffer: c_ulong,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct ForeignMemoryMap {
|
||||
pub domid: u16,
|
||||
pub map: MemoryMap,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct VcpuGuestContextFpuCtx {
|
||||
@ -402,8 +451,8 @@ impl Default for VcpuGuestContextFpuCtx {
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub struct CpuUserRegs {
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct x8664CpuUserRegs {
|
||||
pub r15: u64,
|
||||
pub r14: u64,
|
||||
pub r13: u64,
|
||||
@ -442,7 +491,6 @@ pub struct CpuUserRegs {
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub struct TrapInfo {
|
||||
pub vector: u8,
|
||||
pub flags: u8,
|
||||
@ -452,11 +500,11 @@ pub struct TrapInfo {
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub struct VcpuGuestContext {
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct x8664VcpuGuestContext {
|
||||
pub fpu_ctx: VcpuGuestContextFpuCtx,
|
||||
pub flags: u64,
|
||||
pub user_regs: CpuUserRegs,
|
||||
pub user_regs: x8664CpuUserRegs,
|
||||
pub trap_ctx: [TrapInfo; 256],
|
||||
pub ldt_base: u64,
|
||||
pub ldt_ents: u64,
|
||||
@ -475,10 +523,9 @@ pub struct VcpuGuestContext {
|
||||
pub gs_base_user: u64,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
impl Default for VcpuGuestContext {
|
||||
impl Default for x8664VcpuGuestContext {
|
||||
fn default() -> Self {
|
||||
VcpuGuestContext {
|
||||
Self {
|
||||
fpu_ctx: Default::default(),
|
||||
flags: 0,
|
||||
user_regs: Default::default(),
|
||||
@ -504,8 +551,7 @@ impl Default for VcpuGuestContext {
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub struct CpuUserRegs {
|
||||
pub struct Arm64CpuUserRegs {
|
||||
pub x0: u64,
|
||||
pub x1: u64,
|
||||
pub x2: u64,
|
||||
@ -551,10 +597,9 @@ pub struct CpuUserRegs {
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub struct VcpuGuestContext {
|
||||
pub struct Arm64VcpuGuestContext {
|
||||
pub flags: u32,
|
||||
pub user_regs: CpuUserRegs,
|
||||
pub user_regs: x8664CpuUserRegs,
|
||||
pub sctlr: u64,
|
||||
pub ttbcr: u64,
|
||||
pub ttbr0: u64,
|
||||
@ -562,7 +607,10 @@ pub struct VcpuGuestContext {
|
||||
}
|
||||
|
||||
pub union VcpuGuestContextAny {
|
||||
pub value: VcpuGuestContext,
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub value: Arm64VcpuGuestContext,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub value: x8664VcpuGuestContext,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
@ -582,3 +630,174 @@ pub struct EvtChnAllocUnbound {
|
||||
pub remote_dom: u16,
|
||||
pub port: u32,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub struct E820Entry {
|
||||
pub addr: u64,
|
||||
pub size: u64,
|
||||
pub typ: u32,
|
||||
}
|
||||
|
||||
pub const E820_MAX: u32 = 1024;
|
||||
pub const E820_RAM: u32 = 1;
|
||||
pub const E820_RESERVED: u32 = 2;
|
||||
pub const E820_ACPI: u32 = 3;
|
||||
pub const E820_NVS: u32 = 4;
|
||||
pub const E820_UNUSABLE: u32 = 5;
|
||||
|
||||
pub const PHYSDEVOP_MAP_PIRQ: u64 = 13;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default, Clone, Copy, Debug)]
|
||||
pub struct PhysdevMapPirq {
|
||||
pub domid: u16,
|
||||
pub typ: c_int,
|
||||
pub index: c_int,
|
||||
pub pirq: c_int,
|
||||
pub bus: c_int,
|
||||
pub devfn: c_int,
|
||||
pub entry_nr: u16,
|
||||
pub table_base: u64,
|
||||
}
|
||||
|
||||
pub const DOMCTL_DEV_RDM_RELAXED: u32 = 1;
|
||||
pub const DOMCTL_DEV_PCI: u32 = 0;
|
||||
pub const DOMCTL_DEV_DT: u32 = 1;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default, Clone, Copy, Debug)]
|
||||
pub struct PciAssignDevice {
|
||||
pub sbdf: u32,
|
||||
pub padding: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default, Clone, Copy, Debug)]
|
||||
pub struct AssignDevice {
|
||||
pub device: u32,
|
||||
pub flags: u32,
|
||||
pub pci_assign_device: PciAssignDevice,
|
||||
}
|
||||
|
||||
pub const DOMID_IO: u32 = 0x7FF1;
|
||||
pub const MEMFLAGS_POPULATE_ON_DEMAND: u32 = 1 << 16;
|
||||
|
||||
pub struct PodTarget {
|
||||
pub target_pages: u64,
|
||||
pub total_pages: u64,
|
||||
pub pod_cache_pages: u64,
|
||||
pub pod_entries: u64,
|
||||
pub domid: u16,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default, Clone, Copy, Debug)]
|
||||
pub struct HvmParam {
|
||||
pub domid: u16,
|
||||
pub pad: u8,
|
||||
pub index: u32,
|
||||
pub value: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct HvmContext {
|
||||
pub size: u32,
|
||||
pub buffer: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PagingMempool {
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SysctlCputopo {
|
||||
pub core: u32,
|
||||
pub socket: u32,
|
||||
pub node: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SysctlSetCpuFreqGov {
|
||||
pub scaling_governor: [u8; 16],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub union SysctlPmOpValue {
|
||||
pub set_gov: SysctlSetCpuFreqGov,
|
||||
pub opt_smt: u32,
|
||||
pub pad: [u8; 128],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SysctlPmOp {
|
||||
pub cmd: u32,
|
||||
pub cpuid: u32,
|
||||
pub value: SysctlPmOpValue,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SysctlCputopoinfo {
|
||||
pub num_cpus: u32,
|
||||
pub handle: c_ulong,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub union SysctlValue {
|
||||
pub cputopoinfo: SysctlCputopoinfo,
|
||||
pub pm_op: SysctlPmOp,
|
||||
pub phys_info: SysctlPhysinfo,
|
||||
pub pad: [u8; 128],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Sysctl {
|
||||
pub cmd: u32,
|
||||
pub interface_version: u32,
|
||||
pub value: SysctlValue,
|
||||
}
|
||||
|
||||
pub const XEN_SYSCTL_PHYSINFO: u32 = 3;
|
||||
pub const XEN_SYSCTL_PM_OP: u32 = 12;
|
||||
pub const XEN_SYSCTL_CPUTOPOINFO: u32 = 16;
|
||||
|
||||
pub const XEN_SYSCTL_MIN_INTERFACE_VERSION: u32 = 0x00000015;
|
||||
pub const XEN_SYSCTL_MAX_INTERFACE_VERSION: u32 = 0x00000020;
|
||||
pub const XEN_SYSCTL_PM_OP_SET_SCHED_OPT_STMT: u32 = 0x21;
|
||||
pub const XEN_SYSCTL_PM_OP_ENABLE_TURBO: u32 = 0x26;
|
||||
pub const XEN_SYSCTL_PM_OP_DISABLE_TURBO: u32 = 0x27;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum CpuId {
|
||||
All,
|
||||
Single(u32),
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct SysctlPhysinfo {
|
||||
pub threads_per_core: u32,
|
||||
pub cores_per_socket: u32,
|
||||
pub nr_cpus: u32,
|
||||
pub max_cpu_id: u32,
|
||||
pub nr_nodes: u32,
|
||||
pub max_node_id: u32,
|
||||
pub cpu_khz: u32,
|
||||
pub capabilities: u32,
|
||||
pub arch_capabilities: u32,
|
||||
pub pad: u32,
|
||||
pub total_pages: u64,
|
||||
pub free_pages: u64,
|
||||
pub scrub_pages: u64,
|
||||
pub outstanding_pages: u64,
|
||||
pub max_mfn: u64,
|
||||
pub hw_cap: [u32; 8],
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "krata-xenclient"
|
||||
description = "An implementation of Xen userspace for krata."
|
||||
description = "An implementation of Xen userspace for krata"
|
||||
license.workspace = true
|
||||
version.workspace = true
|
||||
homepage.workspace = true
|
||||
@ -10,19 +10,16 @@ resolver = "2"
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
elf = { workspace = true }
|
||||
flate2 = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
log = { workspace = true }
|
||||
krata-xencall = { path = "../xencall", version = "^0.0.10" }
|
||||
krata-xenstore = { path = "../xenstore", version = "^0.0.10" }
|
||||
memchr = { workspace = true }
|
||||
nix = { workspace = true }
|
||||
slice-copy = { workspace = true }
|
||||
krata-xencall = { path = "../xencall", version = "^0.0.15" }
|
||||
krata-xenplatform = { path = "../xenplatform", version = "^0.0.15" }
|
||||
krata-xenstore = { path = "../xenstore", version = "^0.0.15" }
|
||||
regex = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
xz2 = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = { workspace = true }
|
||||
@ -34,3 +31,7 @@ name = "xenclient"
|
||||
[[example]]
|
||||
name = "xenclient-boot"
|
||||
path = "examples/boot.rs"
|
||||
|
||||
[[example]]
|
||||
name = "xenclient-pci"
|
||||
path = "examples/pci.rs"
|
||||
|
@ -1,7 +1,15 @@
|
||||
use std::{env, process};
|
||||
use tokio::fs;
|
||||
use uuid::Uuid;
|
||||
use xenclient::error::Result;
|
||||
use xenclient::{DomainConfig, XenClient};
|
||||
use xenplatform::domain::BaseDomainConfig;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
type RuntimePlatform = xenplatform::x86pv::X86PvPlatform;
|
||||
|
||||
#[cfg(not(target_arch = "x86_64"))]
|
||||
type RuntimePlatform = xenplatform::unsupported::UnsupportedPlatform;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
@ -14,23 +22,28 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
let kernel_image_path = args.get(1).expect("argument not specified");
|
||||
let initrd_path = args.get(2).expect("argument not specified");
|
||||
let client = XenClient::open(0).await?;
|
||||
let client = XenClient::new(0, RuntimePlatform::new()).await?;
|
||||
let config = DomainConfig {
|
||||
base: BaseDomainConfig {
|
||||
uuid: Uuid::new_v4(),
|
||||
max_vcpus: 1,
|
||||
mem_mb: 512,
|
||||
enable_iommu: true,
|
||||
kernel: fs::read(&kernel_image_path).await?,
|
||||
initrd: fs::read(&initrd_path).await?,
|
||||
cmdline: "earlyprintk=xen earlycon=xen console=hvc0 init=/init".to_string(),
|
||||
owner_domid: 0,
|
||||
},
|
||||
backend_domid: 0,
|
||||
name: "xenclient-test".to_string(),
|
||||
max_vcpus: 1,
|
||||
mem_mb: 512,
|
||||
kernel: fs::read(&kernel_image_path).await?,
|
||||
initrd: fs::read(&initrd_path).await?,
|
||||
cmdline: "debug elevator=noop".to_string(),
|
||||
use_console_backend: None,
|
||||
swap_console_backend: None,
|
||||
disks: vec![],
|
||||
channels: vec![],
|
||||
vifs: vec![],
|
||||
pcis: vec![],
|
||||
filesystems: vec![],
|
||||
extra_keys: vec![],
|
||||
extra_rw_paths: vec![],
|
||||
event_channels: vec![],
|
||||
};
|
||||
let created = client.create(&config).await?;
|
||||
println!("created domain {}", created.domid);
|
||||
|
32
crates/xen/xenclient/examples/pci.rs
Normal file
32
crates/xen/xenclient/examples/pci.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use xenclient::pci::*;
|
||||
|
||||
use xenclient::error::Result;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let backend = XenPciBackend::new();
|
||||
if !backend.is_loaded().await? {
|
||||
return Err(xenclient::error::Error::GenericError(
|
||||
"xen-pciback module not loaded".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
println!("assignable devices:");
|
||||
for device in backend.list_devices().await? {
|
||||
let is_assigned = backend.is_assigned(&device).await?;
|
||||
let has_slot = backend.has_slot(&device).await?;
|
||||
println!("{} slot={} assigned={}", device, has_slot, is_assigned);
|
||||
let resources = backend.read_resources(&device).await?;
|
||||
for resource in resources {
|
||||
println!(
|
||||
" resource start={:#x} end={:#x} flags={:#x} bar-io={}",
|
||||
resource.start,
|
||||
resource.end,
|
||||
resource.flags,
|
||||
resource.is_bar_io()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user