diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d16783b358b..6893907843a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,179 +14,15 @@ concurrency: cancel-in-progress: true jobs: - code-tests: - name: Code - runs-on: ubuntu-24.04 - strategy: - fail-fast: false - matrix: - go: - - oldstable - - stable - - tip - steps: - - name: Checkout - uses: actions/checkout@v5 - with: - # Differential ShellCheck requires full git history - fetch-depth: 0 - - - name: Dependency Review - uses: actions/dependency-review-action@v4 - if: github.event_name == 'pull_request' - - - id: ShellCheck - name: Differential ShellCheck - uses: redhat-plumbers-in-action/differential-shellcheck@v5 - env: - SHELLCHECK_OPTS: --shell sh - with: - token: ${{ secrets.GITHUB_TOKEN }} - if: github.event_name == 'pull_request' && matrix.go == 'stable' - - - name: Upload artifact with ShellCheck defects in SARIF format - uses: actions/upload-artifact@v4 - with: - name: Differential ShellCheck SARIF - path: ${{ steps.ShellCheck.outputs.sarif }} - if: github.event_name == 'pull_request' && matrix.go == 'stable' - - - name: Install Go (${{ matrix.go }}) - uses: actions/setup-go@v6 - with: - go-version: ${{ matrix.go }} - if: matrix.go != 'tip' - - - name: Install Go (stable) - uses: actions/setup-go@v6 - with: - go-version: stable - if: matrix.go == 'tip' - - - name: Install Go (tip) - run: | - go install golang.org/dl/gotip@latest - gotip download - ~/sdk/gotip/bin/go version - echo "PATH=$HOME/go/bin:$HOME/sdk/gotip/bin/:$PATH" >> $GITHUB_ENV - if: matrix.go == 'tip' - - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install --no-install-recommends -y \ - curl \ - gettext \ - git \ - libacl1-dev \ - libcap-dev \ - libdbus-1-dev \ - libcowsql-dev \ - liblxc-dev \ - lxc-templates \ - libseccomp-dev \ - libselinux-dev \ - libsqlite3-dev \ - libtool \ - libudev-dev \ - make \ - pipx \ - pkg-config \ - shellcheck - - # With pipx >= 1.5.0, we could use pipx --global instead. - PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin \ - pipx install codespell flake8 - - - name: Fix repository permissions - run: | - sudo chown -R runner:docker . - - - name: Check compatible min Go version - run: | - go mod tidy - - - name: Download go dependencies - run: | - go mod download - - - name: Run Incus build - run: | - make - - - name: Run static analysis - env: - GITHUB_BEFORE: ${{ github.event.before }} - run: | - make static-analysis - - - name: Unit tests (all) - run: | - sudo --preserve-env=CGO_CFLAGS,CGO_LDFLAGS,CGO_LDFLAGS_ALLOW,LD_LIBRARY_PATH LD_LIBRARY_PATH=${LD_LIBRARY_PATH} env "PATH=${PATH}" go test ./... - system-tests: name: System strategy: fail-fast: false matrix: - go: - - stable suite: - cluster - standalone - backend: - - dir - - btrfs - - lvm - - zfs - - ceph - - random - os: - - ubuntu-24.04 - include: - - go: oldstable - suite: cluster - backend: dir - os: ubuntu-24.04 - - go: oldstable - suite: standalone - backend: dir - os: ubuntu-24.04 - - go: tip - suite: cluster - backend: dir - os: ubuntu-24.04 - - go: tip - suite: standalone - backend: dir - os: ubuntu-24.04 - - - go: oldstable - suite: cluster - backend: dir - os: ubuntu-24.04-arm - - go: oldstable - suite: standalone - backend: dir - os: ubuntu-24.04-arm - - go: stable - suite: cluster - backend: dir - os: ubuntu-24.04-arm - - go: stable - suite: standalone - backend: dir - os: ubuntu-24.04-arm - - go: tip - suite: cluster - backend: dir - os: ubuntu-24.04-arm - - go: tip - suite: standalone - backend: dir - os: ubuntu-24.04-arm - runs-on: ${{ matrix.os }} - + runs-on: ubuntu-24.04 steps: - name: Performance tuning run: | @@ -230,25 +66,10 @@ jobs: - name: Checkout uses: actions/checkout@v5 - - name: Install Go (${{ matrix.go }}) - uses: actions/setup-go@v6 - with: - go-version: ${{ matrix.go }} - if: matrix.go != 'tip' - - name: Install Go (stable) uses: actions/setup-go@v6 with: go-version: stable - if: matrix.go == 'tip' - - - name: Install Go (tip) - run: | - go install golang.org/dl/gotip@latest - gotip download - ~/sdk/gotip/bin/go version - echo "PATH=$HOME/go/bin:$HOME/sdk/gotip/bin/:$PATH" >> $GITHUB_ENV - if: matrix.go == 'tip' - name: Install dependencies run: | @@ -339,60 +160,7 @@ jobs: run: | make - - name: Setup MicroCeph - if: ${{ matrix.backend == 'ceph' }} - run: | - set -x - - # If the rootfs and the ephemeral part are on the same physical disk, giving the whole - # disk to microceph would wipe our rootfs. Since it is pretty rare for GitHub Action - # runners to have a single disk, we immediately bail rather than trying to gracefully - # handle it. Once snapd releases with https://github.com/snapcore/snapd/pull/13150, - # we will be able to stop worrying about that special case. - if [ "$(stat -c '%d' /)" = "$(stat -c '%d' /mnt)" ]; then - echo "FAIL: rootfs and ephemeral part on the same disk, aborting" - exit 1 - fi - - sudo apt-get install --no-install-recommends -y snapd - sudo snap install microceph --channel=quincy/stable - sudo apt-get install --no-install-recommends -y ceph-common - sudo microceph cluster bootstrap - sudo microceph.ceph config set global osd_pool_default_size 1 - sudo microceph.ceph config set global mon_allow_pool_delete true - sudo microceph.ceph config set global osd_memory_target 939524096 - sudo microceph.ceph osd crush rule rm replicated_rule - sudo microceph.ceph osd crush rule create-replicated replicated default osd - for flag in nosnaptrim noscrub nobackfill norebalance norecover noscrub nodeep-scrub; do - sudo microceph.ceph osd set $flag - done - # Repurpose the ephemeral disk for ceph OSD. - sudo swapoff /mnt/swapfile - ephemeral_disk="$(findmnt --noheadings --output SOURCE --target /mnt | sed 's/[0-9]\+$//')" - sudo umount /mnt - sudo microceph disk add --wipe "${ephemeral_disk}" - sudo rm -rf /etc/ceph - sudo ln -s /var/snap/microceph/current/conf/ /etc/ceph - sudo microceph enable rgw - sudo microceph.ceph osd pool create cephfs_meta 32 - sudo microceph.ceph osd pool create cephfs_data 32 - sudo microceph.ceph fs new cephfs cephfs_meta cephfs_data - sudo microceph.ceph fs ls - sleep 30 - sudo microceph.ceph status - # Wait until there are no more "unkowns" pgs - for _ in $(seq 60); do - if sudo microceph.ceph pg stat | grep -wF unknown; then - sleep 1 - else - break - fi - done - sudo microceph.ceph status - sudo rm -f /snap/bin/rbd - - name: Setup LINSTOR - if: ${{ matrix.backend == 'linstor' }} run: | set -x @@ -440,7 +208,7 @@ jobs: sudo nft add rule inet filter output ip daddr 45.45.148.8 reject sudo nft add rule inet filter output ip6 daddr 2602:fc62:a:1::8 reject - - name: "Run system tests (${{ matrix.go }}, ${{ matrix.suite }}, ${{ matrix.backend }})" + - name: "Run system tests (stable, ${{ matrix.suite }}, linstor)" env: CGO_LDFLAGS_ALLOW: "(-Wl,-wrap,pthread_create)|(-Wl,-z,now)" INCUS_CEPH_CLUSTER: "ceph" @@ -456,148 +224,4 @@ jobs: chmod +x ~ echo "root:1000000:1000000000" | sudo tee /etc/subuid /etc/subgid cd test - sudo --preserve-env=PATH,GOPATH,GITHUB_ACTIONS,INCUS_VERBOSE,INCUS_BACKEND,INCUS_CEPH_CLUSTER,INCUS_CEPH_CEPHFS,INCUS_CEPH_CEPHOBJECT_RADOSGW,INCUS_LINSTOR_LOCAL_SATELLITE,INCUS_LINSTOR_CLUSTER,INCUS_OFFLINE,INCUS_SKIP_TESTS,INCUS_REQUIRED_TESTS, INCUS_BACKEND=${{ matrix.backend }} ./main.sh ${{ matrix.suite }} - - client: - name: Client - strategy: - fail-fast: false - matrix: - go: - - oldstable - - stable - os: - - ubuntu-latest - - macos-latest - - windows-latest - runs-on: ${{ matrix.os }} - - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Install Go - uses: actions/setup-go@v6 - with: - go-version: ${{ matrix.go }} - - - name: Create build directory - run: | - mkdir bin - - - name: Build static incus (x86_64) - env: - CGO_ENABLED: 0 - GOARCH: amd64 - run: | - go build -o bin/incus.x86_64 ./cmd/incus - - - name: Build static incus (aarch64) - env: - CGO_ENABLED: 0 - GOARCH: arm64 - run: | - go build -o bin/incus.aarch64 ./cmd/incus - - - name: Build static incus-agent (x86_64) - if: runner.os == 'Linux' || runner.os == 'Windows' - env: - CGO_ENABLED: 0 - GOARCH: amd64 - run: | - go build -o bin/incus-agent.x86_64 ./cmd/incus-agent - - - name: Build static incus-agent (aarch64) - if: runner.os == 'Linux' || runner.os == 'Windows' - env: - CGO_ENABLED: 0 - GOARCH: arm64 - run: | - go build -o bin/incus-agent.aarch64 ./cmd/incus-agent - - - name: Build static incus-migrate - if: runner.os == 'Linux' - env: - CGO_ENABLED: 0 - run: | - GOARCH=amd64 go build -o bin/incus-migrate.x86_64 ./cmd/incus-migrate - GOARCH=arm64 go build -o bin/incus-migrate.aarch64 ./cmd/incus-migrate - - - name: Build static lxd-to-incus - if: runner.os == 'Linux' - env: - CGO_ENABLED: 0 - run: | - GOARCH=amd64 go build -o bin/lxd-to-incus.x86_64 ./cmd/lxd-to-incus - GOARCH=arm64 go build -o bin/lxd-to-incus.aarch64 ./cmd/lxd-to-incus - - - name: Unit tests (client) - env: - CGO_ENABLED: 0 - run: go test -v ./client/... - - - name: Unit tests (incus) - env: - CGO_ENABLED: 0 - run: go test -v ./cmd/incus/... - - - name: Unit tests (shared) - env: - CGO_ENABLED: 0 - run: go test -v ./shared/... - - - name: Upload incus client artifacts - if: matrix.go == 'stable' - uses: actions/upload-artifact@v4 - continue-on-error: true - with: - name: ${{ runner.os }} - path: bin/ - - documentation: - name: Documentation - runs-on: ubuntu-24.04 - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Install Go - uses: actions/setup-go@v6 - with: - go-version: stable - - - name: Install dependencies - run: | - sudo apt-get install aspell aspell-en - sudo snap install mdl - - - name: Run markdown linter - run: | - make doc-lint - - - name: Run spell checker - run: | - make doc-spellcheck - - - name: Run inclusive naming checker - uses: get-woke/woke-action@v0 - with: - fail-on-error: true - woke-args: "*.md **/*.md -c https://github.com/canonical/Inclusive-naming/raw/main/config.yml" - - - name: Run link checker - run: | - make doc-linkcheck - - - name: Build docs (Sphinx) - run: make doc - - - name: Print warnings (Sphinx) - run: if [ -s doc/.sphinx/warnings.txt ]; then cat doc/.sphinx/warnings.txt; exit 1; fi - - - name: Upload documentation artifacts - if: always() - uses: actions/upload-artifact@v4 - with: - name: documentation - path: doc/html + sudo --preserve-env=PATH,GOPATH,GITHUB_ACTIONS,INCUS_VERBOSE,INCUS_BACKEND,INCUS_CEPH_CLUSTER,INCUS_CEPH_CEPHFS,INCUS_CEPH_CEPHOBJECT_RADOSGW,INCUS_LINSTOR_LOCAL_SATELLITE,INCUS_LINSTOR_CLUSTER,INCUS_OFFLINE,INCUS_SKIP_TESTS,INCUS_REQUIRED_TESTS, INCUS_BACKEND=linstor ./main.sh ${{ matrix.suite }} diff --git a/Makefile b/Makefile index beee8a30bc5..1903fb69c50 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ endif .PHONY: update-ovsdb update-ovsdb: - go install github.com/ovn-org/libovsdb/cmd/modelgen@main + go install github.com/ovn-kubernetes/libovsdb/cmd/modelgen@main rm -Rf internal/server/network/ovs/schema mkdir internal/server/network/ovs/schema diff --git a/cmd/incus-agent/events.go b/cmd/incus-agent/events.go index c9f37ab31e8..d5ac887fba2 100644 --- a/cmd/incus-agent/events.go +++ b/cmd/incus-agent/events.go @@ -161,6 +161,6 @@ func eventsProcess(event api.Event) { logger.Infof("Mounted hotplug %q (Type: %q) to %q", mntSource, "virtiofs", e.Config["path"]) } else if e.Action == "removed" { // Attempt to unmount the disk. - _ = osUmount(mntSource) + _ = osUmount(mntSource, e.Config["path"], "virtiofs") } } diff --git a/cmd/incus-agent/os_darwin.go b/cmd/incus-agent/os_darwin.go new file mode 100644 index 00000000000..368ba8fa4ca --- /dev/null +++ b/cmd/incus-agent/os_darwin.go @@ -0,0 +1,600 @@ +//go:build darwin + +package main + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "io/fs" + "math" + "net" + "os" + "os/exec" + "sort" + "strconv" + "strings" + "syscall" + + "github.com/shirou/gopsutil/v4/cpu" + "github.com/shirou/gopsutil/v4/disk" + "github.com/shirou/gopsutil/v4/mem" + psUtilNet "github.com/shirou/gopsutil/v4/net" + "github.com/shirou/gopsutil/v4/process" + "golang.org/x/sys/unix" + + "github.com/lxc/incus/v6/internal/server/metrics" + "github.com/lxc/incus/v6/internal/version" + "github.com/lxc/incus/v6/shared/api" + "github.com/lxc/incus/v6/shared/logger" + "github.com/lxc/incus/v6/shared/subprocess" +) + +var ( + osShutdownSignal = os.Interrupt + osBaseWorkingDirectory = "/" + osMetricsSupported = true + osGuestAPISupport = false +) + +func parseBytes(b []byte) string { + n := bytes.IndexByte(b, 0) + if n < 0 { + n = len(b) + } + + return string(b[:n]) +} + +func osGetEnvironment() (*api.ServerEnvironment, error) { + uname := unix.Utsname{} + err := unix.Uname(&uname) + if err != nil { + return nil, err + } + + env := &api.ServerEnvironment{ + Kernel: parseBytes(uname.Sysname[:]), + KernelArchitecture: parseBytes(uname.Machine[:]), + KernelVersion: parseBytes(uname.Release[:]), + Server: "incus-agent", + ServerPid: os.Getpid(), + ServerVersion: version.Version, + ServerName: parseBytes(uname.Nodename[:]), + } + + return env, nil +} + +func osLoadModules() error { + // No OS drivers to load on Darwin. + return nil +} + +func osMountShared(src string, dst string, fstype string, opts []string) error { + if fstype != "9p" { + return errors.New("Only 9p shares are supported on Darwin") + } + + // 9p shares behave strangely on Darwin, as they don't get mounted in the file system, but rather + // as volumes, and have their own subcommand that doesn't conform to the mount frontend. + // Bind mounts are not natively supported, so we have to mount the 9p share, then symlink it. + + // Convert relative mounts to absolute from / otherwise dir creation fails or mount fails. + if !strings.HasPrefix(dst, "/") { + dst = fmt.Sprintf("/%s", dst) + } + + // If the path exists and is neither an empty directory nor a broken symbolic link to a volume + // (indicating with high probability a previous share mount which we didn't clean up properly), we + // can't safely use it. + stat, err := os.Lstat(dst) + if err == nil { + if stat.IsDir() { + // Handle directories, failing if not empty. + entries, err := os.ReadDir(dst) + if err != nil { + return fmt.Errorf("Failed to open directory %s: %w", dst, err) + } + + if len(entries) > 0 { + return errors.New("Unable to mount shares on non-empty directories") + } + } else if stat.Mode()&fs.ModeSymlink != 0 { + // Handle symbolic links, failinks if not broken. + // Try to follow the link. + _, err := os.Stat(dst) + if err == nil { + return fmt.Errorf("Unable to mount shares on working symbolic link %s", dst) + } + } else { + return fmt.Errorf("Mount destination %s exists and is not empty", dst) + } + + err = os.Remove(dst) + if err != nil { + return fmt.Errorf("Failed to prepare destination %s: %w", dst, err) + } + } else if !errors.Is(err, fs.ErrNotExist) { + return err + } + + _, err = subprocess.RunCommand("mount_9p", src) + if err != nil { + return err + } + + // Volume naming is very predictable. If a disk has the same name as a 9p share, `mount_9p` fails. + return os.Symlink("/Volumes/"+src, dst) +} + +// osUmount is currently not used, but it is implemented just in case. +func osUmount(src string, dst string, fstype string) error { + if fstype != "9p" { + return errors.New("Only 9p shares are supported on Darwin") + } + + // First, remove the symlink. + err := os.Remove(dst) + if err != nil { + return err + } + + // Then, unmount the share. + _, err = subprocess.RunCommand("umount", "/Volumes/"+src) + return err +} + +func osGetCPUMetrics(d *Daemon) ([]metrics.CPUMetrics, error) { + cpuTimes, err := cpu.Times(true) + if err != nil { + return nil, err + } + + cpuMetrics := make([]metrics.CPUMetrics, 0, len(cpuTimes)) + for _, cpuTime := range cpuTimes { + cpuMetrics = append(cpuMetrics, metrics.CPUMetrics{ + CPU: cpuTime.CPU, + SecondsUser: cpuTime.User, + SecondsNice: cpuTime.Nice, + SecondsSystem: cpuTime.System, + SecondsIdle: cpuTime.Idle, + SecondsIOWait: cpuTime.Iowait, + SecondsIRQ: cpuTime.Irq, + SecondsSoftIRQ: cpuTime.Softirq, + SecondsSteal: cpuTime.Steal, + }) + } + + return cpuMetrics, nil +} + +func osGetDiskMetrics(d *Daemon) ([]metrics.DiskMetrics, error) { + counters, err := disk.IOCounters() + if err != nil { + return nil, err + } + + devices := make([]string, 0, len(counters)) + for device := range counters { + devices = append(devices, device) + } + + sort.Strings(devices) + + diskMetrics := make([]metrics.DiskMetrics, 0, len(devices)) + for _, device := range devices { + counter := counters[device] + diskMetrics = append(diskMetrics, metrics.DiskMetrics{ + Device: counter.Name, + ReadBytes: counter.ReadBytes, + ReadsCompleted: counter.ReadCount, + WrittenBytes: counter.WriteBytes, + WritesCompleted: counter.WriteCount, + }) + } + + return diskMetrics, nil +} + +func osGetFilesystemMetrics(d *Daemon) ([]metrics.FilesystemMetrics, error) { + partitions, err := disk.Partitions(true) + if err != nil { + return nil, err + } + + sort.Slice(partitions, func(i, j int) bool { + return partitions[i].Mountpoint < partitions[j].Mountpoint + }) + + fsMetrics := make([]metrics.FilesystemMetrics, 0, len(partitions)) + for _, partition := range partitions { + var stat syscall.Statfs_t + err = syscall.Statfs(partition.Mountpoint, &stat) + if err != nil { + continue + } + + bsize := uint64(stat.Bsize) + + fsMetrics = append(fsMetrics, metrics.FilesystemMetrics{ + Device: partition.Device, + Mountpoint: partition.Mountpoint, + FSType: partition.Fstype, + AvailableBytes: stat.Bavail * bsize, + FreeBytes: stat.Bfree * bsize, + SizeBytes: stat.Blocks * bsize, + }) + } + + return fsMetrics, nil +} + +func osGetMemoryMetrics(d *Daemon) (metrics.MemoryMetrics, error) { + virtualMemory, err := mem.VirtualMemory() + if err != nil { + return metrics.MemoryMetrics{}, err + } + + swapMemory, err := mem.SwapMemory() + if err != nil { + return metrics.MemoryMetrics{}, err + } + + return metrics.MemoryMetrics{ + ActiveAnonBytes: 0, + ActiveFileBytes: 0, + ActiveBytes: virtualMemory.Active, + CachedBytes: virtualMemory.Cached, + DirtyBytes: virtualMemory.Dirty, + HugepagesFreeBytes: virtualMemory.HugePagesFree * virtualMemory.HugePageSize, + HugepagesTotalBytes: virtualMemory.HugePagesTotal * virtualMemory.HugePageSize, + InactiveAnonBytes: 0, + InactiveFileBytes: 0, + InactiveBytes: virtualMemory.Inactive, + MappedBytes: virtualMemory.Mapped, + MemAvailableBytes: virtualMemory.Available, + MemFreeBytes: virtualMemory.Free, + MemTotalBytes: virtualMemory.Total, + RSSBytes: 0, + ShmemBytes: virtualMemory.Shared, + SwapBytes: swapMemory.Total, + UnevictableBytes: 0, + WritebackBytes: virtualMemory.WriteBack, + OOMKills: 0, + }, nil +} + +func osGetCPUState() api.InstanceStateCPU { + cpuState := api.InstanceStateCPU{} + + cpuTimes, err := cpu.Times(false) + if err != nil || len(cpuTimes) < 1 { + cpuState.Usage = -1 + } else { + cpuTime := cpuTimes[0] + cpuState.Usage = int64(math.Round((cpuTime.System + cpuTime.User) * 1e9)) + } + + return cpuState +} + +func osGetMemoryState() api.InstanceStateMemory { + memory := api.InstanceStateMemory{} + + virtualMemory, err := mem.VirtualMemory() + if err != nil { + return memory + } + + memory.Usage = int64(virtualMemory.Total - virtualMemory.Free) + memory.Total = int64(virtualMemory.Total) + return memory +} + +func ipScope(ip net.IP) string { + if ip.IsLoopback() { + return "local" + } + + if ip.To4() != nil { + if ip[0] == 169 && ip[1] == 254 { + return "link" + } + + return "global" + } + + if ip[0] == 0xfe && (ip[1]&0xc0) == 0x80 { + return "link" + } + + return "global" +} + +func osGetNetworkState() map[string]api.InstanceStateNetwork { + interfaces, err := psUtilNet.Interfaces() + if err != nil { + return map[string]api.InstanceStateNetwork{} + } + + ioCounters, err := psUtilNet.IOCounters(true) + if err != nil { + return map[string]api.InstanceStateNetwork{} + } + + // Create a map for fast lookup. + counters := make(map[string]psUtilNet.IOCountersStat, len(ioCounters)) + for _, c := range ioCounters { + counters[c.Name] = c + } + + sort.Slice(interfaces, func(i, j int) bool { + return interfaces[i].Name < interfaces[j].Name + }) + + network := make(map[string]api.InstanceStateNetwork, len(interfaces)) + for _, intf := range interfaces { + addrs := make([]api.InstanceStateNetworkAddress, 0, len(intf.Addrs)) + for _, addr := range intf.Addrs { + ip, ipnet, err := net.ParseCIDR(addr.Addr) + if err != nil || ip == nil || ipnet == nil { + continue + } + + family := "inet" + if ip.To4() == nil { + family = "inet6" + } + + ones, _ := ipnet.Mask.Size() + + addrs = append(addrs, api.InstanceStateNetworkAddress{ + Family: family, + Address: ip.String(), + Netmask: strconv.Itoa(ones), + Scope: ipScope(ip), + }) + } + + var cnt api.InstanceStateNetworkCounters + counter, ok := counters[intf.Name] + if ok { + cnt = api.InstanceStateNetworkCounters{ + BytesReceived: int64(counter.BytesRecv), + BytesSent: int64(counter.BytesSent), + PacketsReceived: int64(counter.PacketsRecv), + PacketsSent: int64(counter.PacketsSent), + ErrorsReceived: int64(counter.Errin), + ErrorsSent: int64(counter.Errout), + PacketsDroppedOutbound: int64(counter.Dropout), + PacketsDroppedInbound: int64(counter.Dropin), + } + } + + interfaceState := "down" + interfaceType := "unknown" + for _, flag := range intf.Flags { + if flag == "up" { + interfaceState = "up" + } else if flag == "broadcast" { + interfaceType = "broadcast" + } else if flag == "loopback" { + interfaceType = "loopback" + } else if flag == "pointtopoint" { + interfaceType = "point-to-point" + } + } + + network[intf.Name] = api.InstanceStateNetwork{ + Addresses: addrs, + Counters: cnt, + Hwaddr: intf.HardwareAddr, + HostName: intf.Name, + Mtu: intf.MTU, + State: interfaceState, + Type: interfaceType, + } + } + + return network +} + +func osGetProcessesState() int64 { + processes, err := process.Processes() + if err != nil { + return -1 + } + + return int64(len(processes)) +} + +func macOSVersionName(version string) (string, error) { + parts := strings.Split(version, ".") + var major, minor int + var err error + + if len(parts) > 0 { + major, err = strconv.Atoi(parts[0]) + if err != nil { + return "", err + } + } + + if len(parts) > 1 { + minor, err = strconv.Atoi(parts[1]) + if err != nil { + return "", err + } + } + + switch major { + case 26: + return "Tahoe", nil + case 15: + return "Sequoia", nil + case 14: + return "Sonoma", nil + case 13: + return "Ventura", nil + case 12: + return "Monterey", nil + case 11: + return "Big Sur", nil + case 10: + switch minor { + case 16: + // Apparently, this one can happen. + return "Big Sur", nil + case 15: + return "Catalina", nil + case 14: + return "Mojave", nil + case 13: + return "High Sierra", nil + case 12: + return "Sierra", nil + case 11: + return "El Capitan", nil + case 10: + return "Yosemite", nil + case 9: + return "Mavericks", nil + case 8: + return "Mountain Lion", nil + case 7: + return "Lion", nil + case 6: + return "Snow Leopard", nil + case 5: + return "Leopard", nil + case 4: + return "Tiger", nil + case 3: + return "Panther", nil + case 2: + return "Jaguar", nil + case 1: + return "Puma", nil + case 0: + return "Cheetah", nil + } + } + + return "", errors.New("Unknown macOS version") +} + +func osGetOSState() *api.InstanceStateOSInfo { + swVers, err := subprocess.RunCommand("sw_vers") + if err != nil { + return nil + } + + var productName, productVersion string + for _, line := range strings.Split(strings.TrimSpace(swVers), "\n") { + key, after, found := strings.Cut(line, ":") + if !found { + continue + } + + value := strings.TrimSpace(after) + if key == "ProductName" { + productName = value + } else if key == "ProductVersion" { + productVersion = value + } + } + + // Add the familiar version name if we are dealing with a known macOS version. + if productName == "Mac OS X" || productName == "macOS" { + versionName, err := macOSVersionName(productVersion) + if err == nil { + productVersion += " (" + versionName + ")" + } + } + + uname := unix.Utsname{} + err = unix.Uname(&uname) + if err != nil { + return nil + } + + serverName := parseBytes(uname.Nodename[:]) + + // Prepare OS struct. + osInfo := &api.InstanceStateOSInfo{ + OS: productName, + OSVersion: productVersion, + KernelVersion: parseBytes(uname.Release[:]), + Hostname: serverName, + FQDN: serverName, + } + + return osInfo +} + +func osReconfigureNetworkInterfaces() { + // Agent assisted network reconfiguration isn't currently supported. + return +} + +func osGetInteractiveConsole(s *execWs) (io.ReadWriteCloser, io.ReadWriteCloser, error) { + return nil, nil, errors.New("Only non-interactive exec sessions are currently supported on Darwin") +} + +func osPrepareExecCommand(s *execWs, cmd *exec.Cmd) { + if s.cwd == "" { + cmd.Dir = osBaseWorkingDirectory + } + + return +} + +func osHandleExecControl(control api.InstanceExecControl, s *execWs, pty io.ReadWriteCloser, cmd *exec.Cmd, l logger.Logger) { + // Ignore control messages. + return +} + +// osExitStatus is is the same as linux.ExitStatus for Darwin. +func osExitStatus(err error) (int, error) { + if err == nil { + return 0, err // No error exit status. + } + + var exitErr *exec.ExitError + + // Detect and extract ExitError to check the embedded exit status. + if errors.As(err, &exitErr) { + // If the process was signaled, extract the signal. + status, isWaitStatus := exitErr.Sys().(unix.WaitStatus) + if isWaitStatus && status.Signaled() { + return 128 + int(status.Signal()), nil // 128 + n == Fatal error signal "n" + } + + // Otherwise capture the exit status from the command. + return exitErr.ExitCode(), nil + } + + return -1, err // Not able to extract an exit status. +} + +func osExecWrapper(ctx context.Context, pty io.ReadWriteCloser) io.ReadWriteCloser { + return pty +} + +func osGetListener(port int64) (net.Listener, error) { + l, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + return nil, fmt.Errorf("Failed to listen on TCP: %w", err) + } + + logger.Info("Started TCP listener") + + return l, nil +} + +func osSetEnv(post *api.InstanceExecPost, env map[string]string) { + env["PATH"] = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" +} diff --git a/cmd/incus-agent/os_linux.go b/cmd/incus-agent/os_linux.go index ea3b4d53274..f796a7c2e76 100644 --- a/cmd/incus-agent/os_linux.go +++ b/cmd/incus-agent/os_linux.go @@ -132,7 +132,7 @@ func osMountShared(src string, dst string, fstype string, opts []string) error { return nil } -func osUmount(src string) error { +func osUmount(src string, dst string, fstype string) error { _, err := subprocess.RunCommand("umount", src) return err } diff --git a/cmd/incus-agent/os_windows.go b/cmd/incus-agent/os_windows.go index 14f7e3fa0da..5a9c472d602 100644 --- a/cmd/incus-agent/os_windows.go +++ b/cmd/incus-agent/os_windows.go @@ -56,7 +56,7 @@ func osMountShared(src string, dst string, fstype string, opts []string) error { return errors.New("Dynamic mounts aren't supported on Windows") } -func osUmount(src string) error { +func osUmount(src string, dst string, fstype string) error { return errors.New("Dynamic mounts aren't supported on Windows") } diff --git a/cmd/incusd/api_cluster.go b/cmd/incusd/api_cluster.go index 3086ea46538..c128faefc1f 100644 --- a/cmd/incusd/api_cluster.go +++ b/cmd/incusd/api_cluster.go @@ -511,6 +511,13 @@ func clusterPutJoin(d *Daemon, r *http.Request, req api.ClusterPut) response.Res UserAgent: version.UserAgent, } + // Always set a proxy function to have cluster traffic bypass any configured HTTP proxy. + proxy := func(req *http.Request) (*url.URL, error) { + return nil, nil + } + + args.Proxy = proxy + // Asynchronously join the cluster. run := func(op *operations.Operation) error { logger.Debug("Running cluster join operation") diff --git a/doc/.wordlist.txt b/doc/.wordlist.txt index 76addb0033b..051b45a606f 100644 --- a/doc/.wordlist.txt +++ b/doc/.wordlist.txt @@ -9,6 +9,7 @@ allocator AMD Ansible Ansible's +API APIs AppArmor ARMv @@ -306,6 +307,7 @@ syscall syscalls sysfs syslog +systemd Tbit TCP Telegraf diff --git a/doc/api-extensions.md b/doc/api-extensions.md index 40c20ff55d8..f78830c4bdb 100644 --- a/doc/api-extensions.md +++ b/doc/api-extensions.md @@ -2907,3 +2907,7 @@ This introduces a new `attached` property to USB devices describing whether they ## `backup_iso` This allows to backup ISO custom volumes by simply copying them. It enables exporting ISO volumes with `incus storage volume export` and get an ISO back. + +## `instance_systemd_credentials` + +This adds two categories of configuration options, `systemd.credential.*` and `systemd.credential-binary.*`, which allow passing systemd credentials through a bind-mounted directory for containers and `SMBIOS Type 11` for virtual machines. diff --git a/doc/config_options.txt b/doc/config_options.txt index 642ed5123a7..d8fbdc81f5f 100644 --- a/doc/config_options.txt +++ b/doc/config_options.txt @@ -1924,6 +1924,20 @@ Specify the kernel modules as a comma-separated list. `SMBIOS Type 11` configuration keys. ``` +```{config:option} systemd.credential-binary.* instance-miscellaneous +:liveupdate: "yes" +:shortdesc: "Systemd credential key/value, where value is Base64 encoded" +:type: "string" +Systemd credential key/value pair passed as a read-only bind mount in containers and as `SMBIOS Type 11` data in virtual machines. The value is Base64 encoded. +``` + +```{config:option} systemd.credential.* instance-miscellaneous +:liveupdate: "yes" +:shortdesc: "Systemd credential key/value" +:type: "string" +Systemd credential key/value pair passed as a read-only bind mount in containers and as `SMBIOS Type 11` data in virtual machines. +``` + ```{config:option} user.* instance-miscellaneous :liveupdate: "yes" :shortdesc: "Free-form user key/value storage" diff --git a/doc/third_party.md b/doc/third_party.md index 8a27e858e34..fbb82b546d4 100644 --- a/doc/third_party.md +++ b/doc/third_party.md @@ -33,3 +33,10 @@ The focus of Distrobuilder is in producing clean images from scratch, as opposed [GARM](https://github.com/cloudbase/garm) is the Github Actions Runner Manager which allows for running self-hosted Github runners. It supports a variety of providers for those runners, including [Incus](https://github.com/cloudbase/garm-provider-incus). + +## Kubernetes +[Kubernetes](https://kubernetes.io), also known as K8s, is an open source system for automating deployment, scaling, and management of containerized applications. +[Cluster API](https://cluster-api.sigs.k8s.io) is a Kubernetes sub-project focused on providing declarative APIs and tooling to simplify provisioning, upgrading, and operating multiple Kubernetes clusters. + +[The Cluster API provider for Incus](https://capn.linuxcontainers.org) is an Infrastructure Provider for Cluster API, which enables deploying Kubernetes clusters on infrastructure operated by Incus. +The provider can be used in single-node development environments for evaluation and testing, but also work with multi-node Incus clusters to deploy and manage production Kubernetes clusters. diff --git a/go.mod b/go.mod index 4dedc1b8d3e..9b534f05908 100644 --- a/go.mod +++ b/go.mod @@ -43,10 +43,11 @@ require ( github.com/opencontainers/umoci v0.5.1 github.com/openfga/go-sdk v0.7.1 github.com/osrg/gobgp/v3 v3.37.0 - github.com/ovn-org/libovsdb v0.7.0 + github.com/ovn-kubernetes/libovsdb v0.8.1 github.com/pierrec/lz4/v4 v4.1.22 github.com/pkg/sftp v1.13.9 github.com/pkg/xattr v0.4.12 + github.com/shirou/gopsutil/v4 v4.25.9 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.10 @@ -85,10 +86,16 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/eapache/channels v1.1.0 // indirect github.com/eapache/queue v1.1.0 // indirect + github.com/ebitengine/purego v0.9.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.26.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/google/go-querystring v1.1.0 // indirect @@ -102,6 +109,8 @@ require ( github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/kr/fs v0.1.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.17 // indirect github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 // indirect @@ -123,6 +132,7 @@ require ( github.com/philhofer/fwd v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.66.1 // indirect @@ -139,10 +149,13 @@ require ( github.com/spf13/viper v1.21.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tinylib/msgp v1.4.0 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect github.com/urfave/cli v1.22.17 // indirect github.com/vbatts/go-mtree v0.6.0 // indirect github.com/vishvananda/netns v0.0.5 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zitadel/logging v0.6.2 // indirect github.com/zitadel/schema v1.3.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect diff --git a/go.sum b/go.sum index 66ab5592de2..8b6bff21acc 100644 --- a/go.sum +++ b/go.sum @@ -119,6 +119,8 @@ github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4 github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k= +github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -139,6 +141,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= @@ -155,6 +159,16 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= @@ -329,6 +343,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lxc/go-lxc v0.0.0-20240606200241-27b3d116511f h1:KnZqnn4R9Ae+jOK7DwacF1CnWEBMSwoXh44owa6j6k4= github.com/lxc/go-lxc v0.0.0-20240606200241-27b3d116511f/go.mod h1:3UTWXVcHfgxE7JM4ZUnsy6bDA8L1vuzwJbJRF6dlB90= github.com/lxc/incus-os/incus-osd v0.0.0-20250925165420-d280ecd80348 h1:FDAIomuU3tktGwGXKxUGyZiDgCDFlCofymH3/qAiY8U= @@ -424,8 +442,8 @@ github.com/openfga/go-sdk v0.7.1 h1:ZFFDRoSWAHcbOzPFUWPLUpoIOJZRoQ6KgJp2vyfB82g= github.com/openfga/go-sdk v0.7.1/go.mod h1:Fu00XYLWkfgmo3PV45EwSOhpaBNcuVMBOdklpKoaazw= github.com/osrg/gobgp/v3 v3.37.0 h1:+ObuOdvj7G7nxrT0fKFta+EAupdWf/q1WzbXydr8IOY= github.com/osrg/gobgp/v3 v3.37.0/go.mod h1:kVHVFy1/fyZHJ8P32+ctvPeJogn9qKwa1YCeMRXXrP0= -github.com/ovn-org/libovsdb v0.7.0 h1:owk3MHhaJ0gs0dWvTBtj7lPGEzbcyPrDYerEFqPXO/Y= -github.com/ovn-org/libovsdb v0.7.0/go.mod h1:dJbxEaalQl83nn904K32FaMjlH/qOObZ0bj4ejQ78AI= +github.com/ovn-kubernetes/libovsdb v0.8.1 h1:M2J8bcJt5mXCom0HqzfEtuHkT80CTSQRcYG7acT8gf4= +github.com/ovn-kubernetes/libovsdb v0.8.1/go.mod h1:ZlnHLzagmLOSvyd9qfxBIZp6wOSOw0IsRsc+6lNUGbU= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= @@ -449,6 +467,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -480,6 +500,8 @@ github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88ee github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shirou/gopsutil/v4 v4.25.9 h1:JImNpf6gCVhKgZhtaAHJ0serfFGtlfIlSC08eaKdTrU= +github.com/shirou/gopsutil/v4 v4.25.9/go.mod h1:gxIxoC+7nQRwUl/xNhutXlD8lq+jxTgpIkEf3rADHL8= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -538,6 +560,10 @@ github.com/tj/go-buffer v1.1.0/go.mod h1:iyiJpfFcR2B9sXu7KvjbT9fpM4mOelRSDTbntVj github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao= github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ= @@ -554,6 +580,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zitadel/logging v0.6.2 h1:MW2kDDR0ieQynPZ0KIZPrh9ote2WkxfBif5QoARDQcU= github.com/zitadel/logging v0.6.2/go.mod h1:z6VWLWUkJpnNVDSLzrPSQSQyttysKZ6bCRongw0ROK4= github.com/zitadel/oidc/v3 v3.44.0 h1:wxpZm/VNQrWHGSB4Ld1rMcjpZvExHz+ikbNhzKyJOck= @@ -743,6 +771,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -764,6 +793,7 @@ golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/internal/instance/config.go b/internal/instance/config.go index 7321a4fc677..c53a9d53d50 100644 --- a/internal/instance/config.go +++ b/internal/instance/config.go @@ -1590,6 +1590,26 @@ func ConfigKeyChecker(key string, instanceType api.InstanceType) (func(value str return validate.IsAny, nil } + // gendoc:generate(entity=instance, group=miscellaneous, key=systemd.credential.*) + // Systemd credential key/value pair passed as a read-only bind mount in containers and as `SMBIOS Type 11` data in virtual machines. + // --- + // type: string + // liveupdate: yes + // shortdesc: Systemd credential key/value + if strings.HasPrefix(key, "systemd.credential.") { + return validate.IsAny, nil + } + + // gendoc:generate(entity=instance, group=miscellaneous, key=systemd.credential-binary.*) + // Systemd credential key/value pair passed as a read-only bind mount in containers and as `SMBIOS Type 11` data in virtual machines. The value is Base64 encoded. + // --- + // type: string + // liveupdate: yes + // shortdesc: Systemd credential key/value, where value is Base64 encoded + if strings.HasPrefix(key, "systemd.credential-binary.") { + return validate.IsBase64, nil + } + return nil, fmt.Errorf("Unknown configuration key: %s", key) } diff --git a/internal/server/instance/drivers/driver_lxc.go b/internal/server/instance/drivers/driver_lxc.go index 6f2dd6c6466..7b880ffb90f 100644 --- a/internal/server/instance/drivers/driver_lxc.go +++ b/internal/server/instance/drivers/driver_lxc.go @@ -5,6 +5,7 @@ import ( "bytes" "context" "database/sql" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -1071,6 +1072,16 @@ func (d *lxc) initLXC(config bool) (*liblxc.Container, error) { } } + err = lxcSetConfigItem(cc, "lxc.environment", "CREDENTIALS_DIRECTORY=/dev/.incus-systemd-credentials") + if err != nil { + return nil, err + } + + err = lxcSetConfigItem(cc, "lxc.mount.entry", fmt.Sprintf("%s dev/.incus-systemd-credentials none bind,ro,create=dir 0 0", filepath.Join(d.Path(), "credentials"))) + if err != nil { + return nil, err + } + // Setup NVIDIA runtime if util.IsTrue(d.expandedConfig["nvidia.runtime"]) { hookDir := os.Getenv("INCUS_LXC_HOOK") @@ -2300,6 +2311,12 @@ func (d *lxc) startCommon() (string, []func() error, error) { } } + // Initialize the credentials directory. + err = d.setupCredentials(false) + if err != nil { + return "", nil, err + } + // Override NVIDIA_VISIBLE_DEVICES if we have devices that need it. if len(nvidiaDevices) > 0 { err = lxcSetConfigItem(cc, "lxc.environment", fmt.Sprintf("NVIDIA_VISIBLE_DEVICES=%s", strings.Join(nvidiaDevices, ","))) @@ -4679,6 +4696,7 @@ func (d *lxc) Update(args db.InstanceArgs, userRequested bool) error { d.release() d.cConfig = false _, _ = d.initLXC(true) + _ = d.setupCredentials(true) cgroup.TaskSchedulerTrigger("container", d.name, "changed") } }() @@ -5198,6 +5216,12 @@ func (d *lxc) Update(args db.InstanceArgs, userRequested bool) error { } } } + + // Update the credentials directory. + err = d.setupCredentials(true) + if err != nil { + return err + } } // Re-generate the instance-id if needed. @@ -9209,3 +9233,81 @@ func (d *lxc) ReloadDevice(devName string) error { func (d *lxc) CanLiveMigrate() bool { return util.IsTrue(d.expandedConfig["migration.stateful"]) } + +// setupCredentials sets up the systemd credentials directory. +func (d *lxc) setupCredentials(update bool) error { + credentialsDir := filepath.Join(d.Path(), "credentials") + credentials := map[string][]byte{} + oldCredentials := map[string]bool{} + + var rootUID, rootGID int64 + idmapset, err := d.NextIdmap() + if err != nil { + return err + } + + if idmapset != nil { + rootUID, rootGID = idmapset.ShiftIntoNS(0, 0) + } + + for k, v := range d.expandedConfig { + after, ok := strings.CutPrefix(k, "systemd.credential.") + if ok { + credentials[after] = []byte(v) + continue + } + + after, ok = strings.CutPrefix(k, "systemd.credential-binary.") + if ok { + data, err := base64.RawStdEncoding.DecodeString(strings.TrimRight(v, "=")) + if err != nil { + return fmt.Errorf("Invalid base64 value for %q: %q", k, v) + } + + credentials[after] = data + } + } + + // Cleanup the credentials directory. + if update { + credEntries, err := os.ReadDir(credentialsDir) + if err != nil { + return fmt.Errorf("Failed to list credentials directory: %w", err) + } + + for _, entry := range credEntries { + oldCredentials[entry.Name()] = true + } + } else { + _ = os.RemoveAll(credentialsDir) + + err = internalUtil.MkdirAllOwner(credentialsDir, 0o100, int(rootUID), int(rootGID)) + if err != nil { + return fmt.Errorf("Failed to create credentials directory: %w", err) + } + } + + for k, v := range credentials { + credentialPath := filepath.Join(credentialsDir, k) + err := os.WriteFile(credentialPath, v, 0o400) + if err != nil { + return fmt.Errorf("Failed to write credential %q: %w", k, err) + } + + err = os.Chown(credentialPath, int(rootUID), int(rootGID)) + if err != nil { + return fmt.Errorf("Failed setting permissions for file %q: %w", credentialPath, err) + } + + delete(oldCredentials, k) + } + + for oldCredential := range oldCredentials { + err = os.Remove(filepath.Join(credentialsDir, oldCredential)) + if err != nil { + return fmt.Errorf("Failed to remove credential %q: %w", oldCredential, err) + } + } + + return nil +} diff --git a/internal/server/instance/drivers/driver_qemu.go b/internal/server/instance/drivers/driver_qemu.go index 334d4739dd9..8829811e2a3 100644 --- a/internal/server/instance/drivers/driver_qemu.go +++ b/internal/server/instance/drivers/driver_qemu.go @@ -1751,11 +1751,27 @@ func (d *qemu) start(stateful bool, op *operationlock.InstanceOperation) error { qemuArgs = append(qemuArgs, "-smbios", "type=2,manufacturer=LinuxContainers,product=Incus") for k, v := range d.expandedConfig { - if !strings.HasPrefix(k, "smbios11.") { + var configPrefix, smbiosPrefix string + if strings.HasPrefix(k, "smbios11.") { + configPrefix = "smbios11." + smbiosPrefix = "" + } else if strings.HasPrefix(k, "systemd.credential.") { + configPrefix = "systemd.credential." + smbiosPrefix = "io.systemd.credential:" + } else if strings.HasPrefix(k, "systemd.credential-binary.") { + configPrefix = "systemd.credential-binary." + smbiosPrefix = "io.systemd.credential.binary:" + data, err := base64.RawStdEncoding.DecodeString(strings.TrimRight(v, "=")) + if err != nil { + return fmt.Errorf("Invalid base64 value for %q: %q", k, v) + } + + v = base64.StdEncoding.EncodeToString(data) + } else { continue } - qemuArgs = append(qemuArgs, "-smbios", fmt.Sprintf("type=11,value=%s=%s", strings.TrimPrefix(k, "smbios11."), qemuEscapeCmdline(v))) + qemuArgs = append(qemuArgs, "-smbios", fmt.Sprintf("type=11,value=%s%s=%s", smbiosPrefix, strings.TrimPrefix(k, configPrefix), qemuEscapeCmdline(v))) } } diff --git a/internal/server/instance/instance_utils.go b/internal/server/instance/instance_utils.go index ceae3d48df9..5da4910c27f 100644 --- a/internal/server/instance/instance_utils.go +++ b/internal/server/instance/instance_utils.go @@ -95,6 +95,11 @@ func ValidConfig(sysOS *sys.OS, config map[string]string, expanded bool, instanc if err != nil { return err } + + after, ok := strings.CutPrefix(k, "systemd.credential.") + if ok && config["systemd.credential-binary."+after] != "" { + return fmt.Errorf("Mutually exclusive keys %s and systemd.credential-binary.%s are set", k, after) + } } _, rawSeccomp := config["raw.seccomp"] diff --git a/internal/server/metadata/configuration.json b/internal/server/metadata/configuration.json index 97996b1faf2..c3a2fb03f6f 100644 --- a/internal/server/metadata/configuration.json +++ b/internal/server/metadata/configuration.json @@ -2179,6 +2179,22 @@ "type": "string" } }, + { + "systemd.credential-binary.*": { + "liveupdate": "yes", + "longdesc": "Systemd credential key/value pair passed as a read-only bind mount in containers and as `SMBIOS Type 11` data in virtual machines. The value is Base64 encoded.", + "shortdesc": "Systemd credential key/value, where value is Base64 encoded", + "type": "string" + } + }, + { + "systemd.credential.*": { + "liveupdate": "yes", + "longdesc": "Systemd credential key/value pair passed as a read-only bind mount in containers and as `SMBIOS Type 11` data in virtual machines.", + "shortdesc": "Systemd credential key/value", + "type": "string" + } + }, { "user.*": { "liveupdate": "yes", diff --git a/internal/server/network/driver_ovn.go b/internal/server/network/driver_ovn.go index 5ba3652f899..d4a57d8e902 100644 --- a/internal/server/network/driver_ovn.go +++ b/internal/server/network/driver_ovn.go @@ -20,8 +20,8 @@ import ( "github.com/flosch/pongo2/v6" "github.com/mdlayher/netx/eui64" - ovsClient "github.com/ovn-org/libovsdb/client" - ovsdbModel "github.com/ovn-org/libovsdb/model" + ovsClient "github.com/ovn-kubernetes/libovsdb/client" + ovsdbModel "github.com/ovn-kubernetes/libovsdb/model" incus "github.com/lxc/incus/v6/client" "github.com/lxc/incus/v6/internal/iprange" diff --git a/internal/server/network/ovn/errors.go b/internal/server/network/ovn/errors.go index a2f1bfe4957..bf6f0645a5a 100644 --- a/internal/server/network/ovn/errors.go +++ b/internal/server/network/ovn/errors.go @@ -3,7 +3,7 @@ package ovn import ( "errors" - ovsdbClient "github.com/ovn-org/libovsdb/client" + ovsdbClient "github.com/ovn-kubernetes/libovsdb/client" ) // ErrExists indicates that a DB record already exists. diff --git a/internal/server/network/ovn/events.go b/internal/server/network/ovn/events.go index 57de3714fb1..ad62e029168 100644 --- a/internal/server/network/ovn/events.go +++ b/internal/server/network/ovn/events.go @@ -1,7 +1,7 @@ package ovn import ( - ovsdbModel "github.com/ovn-org/libovsdb/model" + ovsdbModel "github.com/ovn-kubernetes/libovsdb/model" ) // EventHandler represents an OVN database event handler. diff --git a/internal/server/network/ovn/ovn_icnb.go b/internal/server/network/ovn/ovn_icnb.go index c23baf0271c..4375c0cb6c0 100644 --- a/internal/server/network/ovn/ovn_icnb.go +++ b/internal/server/network/ovn/ovn_icnb.go @@ -12,7 +12,7 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/go-logr/logr" - ovsdbClient "github.com/ovn-org/libovsdb/client" + ovsdbClient "github.com/ovn-kubernetes/libovsdb/client" ovnICNB "github.com/lxc/incus/v6/internal/server/network/ovn/schema/ovn-ic-nb" ) diff --git a/internal/server/network/ovn/ovn_icnb_actions.go b/internal/server/network/ovn/ovn_icnb_actions.go index 725b1caf653..b45376a2a20 100644 --- a/internal/server/network/ovn/ovn_icnb_actions.go +++ b/internal/server/network/ovn/ovn_icnb_actions.go @@ -11,8 +11,8 @@ import ( "slices" "strings" - ovsdbClient "github.com/ovn-org/libovsdb/client" - "github.com/ovn-org/libovsdb/ovsdb" + ovsdbClient "github.com/ovn-kubernetes/libovsdb/client" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ovnICNB "github.com/lxc/incus/v6/internal/server/network/ovn/schema/ovn-ic-nb" ) diff --git a/internal/server/network/ovn/ovn_icsb.go b/internal/server/network/ovn/ovn_icsb.go index d6b15b20f4f..e8c11cfe498 100644 --- a/internal/server/network/ovn/ovn_icsb.go +++ b/internal/server/network/ovn/ovn_icsb.go @@ -12,8 +12,8 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/go-logr/logr" - ovsdbClient "github.com/ovn-org/libovsdb/client" - ovsdbModel "github.com/ovn-org/libovsdb/model" + ovsdbClient "github.com/ovn-kubernetes/libovsdb/client" + ovsdbModel "github.com/ovn-kubernetes/libovsdb/model" ovnICSB "github.com/lxc/incus/v6/internal/server/network/ovn/schema/ovn-ic-sb" ) diff --git a/internal/server/network/ovn/ovn_nb.go b/internal/server/network/ovn/ovn_nb.go index 25b28fb3c8b..81a85bc8223 100644 --- a/internal/server/network/ovn/ovn_nb.go +++ b/internal/server/network/ovn/ovn_nb.go @@ -13,8 +13,8 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/go-logr/logr" - ovsdbClient "github.com/ovn-org/libovsdb/client" - ovsdbModel "github.com/ovn-org/libovsdb/model" + ovsdbClient "github.com/ovn-kubernetes/libovsdb/client" + ovsdbModel "github.com/ovn-kubernetes/libovsdb/model" ovnNB "github.com/lxc/incus/v6/internal/server/network/ovn/schema/ovn-nb" ) diff --git a/internal/server/network/ovn/ovn_nb_actions.go b/internal/server/network/ovn/ovn_nb_actions.go index 37535b9aa87..1dda1f68350 100644 --- a/internal/server/network/ovn/ovn_nb_actions.go +++ b/internal/server/network/ovn/ovn_nb_actions.go @@ -10,9 +10,9 @@ import ( "strings" "time" - ovsClient "github.com/ovn-org/libovsdb/client" - ovsModel "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + ovsClient "github.com/ovn-kubernetes/libovsdb/client" + ovsModel "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/lxc/incus/v6/internal/iprange" ovnNB "github.com/lxc/incus/v6/internal/server/network/ovn/schema/ovn-nb" diff --git a/internal/server/network/ovn/ovn_sb.go b/internal/server/network/ovn/ovn_sb.go index 7e44a704a56..370ca807c8c 100644 --- a/internal/server/network/ovn/ovn_sb.go +++ b/internal/server/network/ovn/ovn_sb.go @@ -13,9 +13,9 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/go-logr/logr" - ovsdbCache "github.com/ovn-org/libovsdb/cache" - ovsdbClient "github.com/ovn-org/libovsdb/client" - ovsdbModel "github.com/ovn-org/libovsdb/model" + ovsdbCache "github.com/ovn-kubernetes/libovsdb/cache" + ovsdbClient "github.com/ovn-kubernetes/libovsdb/client" + ovsdbModel "github.com/ovn-kubernetes/libovsdb/model" ovnSB "github.com/lxc/incus/v6/internal/server/network/ovn/schema/ovn-sb" ) diff --git a/internal/server/network/ovn/schema/ovn-ic-nb/connection.go b/internal/server/network/ovn/schema/ovn-ic-nb/connection.go index e0fff1b5d04..c9a1b7690f0 100644 --- a/internal/server/network/ovn/schema/ovn-ic-nb/connection.go +++ b/internal/server/network/ovn/schema/ovn-ic-nb/connection.go @@ -11,7 +11,7 @@ type Connection struct { ExternalIDs map[string]string `ovsdb:"external_ids"` InactivityProbe *int `ovsdb:"inactivity_probe"` IsConnected bool `ovsdb:"is_connected"` - MaxBackoff *int `ovsdb:"max_backoff"` + MaxBackoff *int `ovsdb:"max_backoff" validate:"omitempty,min=1000"` OtherConfig map[string]string `ovsdb:"other_config"` Status map[string]string `ovsdb:"status"` Target string `ovsdb:"target"` diff --git a/internal/server/network/ovn/schema/ovn-ic-nb/model.go b/internal/server/network/ovn/schema/ovn-ic-nb/model.go index def57b85e2b..0d40a2c2659 100644 --- a/internal/server/network/ovn/schema/ovn-ic-nb/model.go +++ b/internal/server/network/ovn/schema/ovn-ic-nb/model.go @@ -6,8 +6,8 @@ package ovsmodel import ( "encoding/json" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // FullDatabaseModel returns the DatabaseModel object to be used in libovsdb diff --git a/internal/server/network/ovn/schema/ovn-ic-sb/connection.go b/internal/server/network/ovn/schema/ovn-ic-sb/connection.go index e0fff1b5d04..c9a1b7690f0 100644 --- a/internal/server/network/ovn/schema/ovn-ic-sb/connection.go +++ b/internal/server/network/ovn/schema/ovn-ic-sb/connection.go @@ -11,7 +11,7 @@ type Connection struct { ExternalIDs map[string]string `ovsdb:"external_ids"` InactivityProbe *int `ovsdb:"inactivity_probe"` IsConnected bool `ovsdb:"is_connected"` - MaxBackoff *int `ovsdb:"max_backoff"` + MaxBackoff *int `ovsdb:"max_backoff" validate:"omitempty,min=1000"` OtherConfig map[string]string `ovsdb:"other_config"` Status map[string]string `ovsdb:"status"` Target string `ovsdb:"target"` diff --git a/internal/server/network/ovn/schema/ovn-ic-sb/datapath_binding.go b/internal/server/network/ovn/schema/ovn-ic-sb/datapath_binding.go index 23aa0823321..3d7a453715d 100644 --- a/internal/server/network/ovn/schema/ovn-ic-sb/datapath_binding.go +++ b/internal/server/network/ovn/schema/ovn-ic-sb/datapath_binding.go @@ -10,5 +10,5 @@ type DatapathBinding struct { UUID string `ovsdb:"_uuid"` ExternalIDs map[string]string `ovsdb:"external_ids"` TransitSwitch string `ovsdb:"transit_switch"` - TunnelKey int `ovsdb:"tunnel_key"` + TunnelKey int `ovsdb:"tunnel_key" validate:"min=1,max=16777215"` } diff --git a/internal/server/network/ovn/schema/ovn-ic-sb/encap.go b/internal/server/network/ovn/schema/ovn-ic-sb/encap.go index eda3bb67cde..dc841da9454 100644 --- a/internal/server/network/ovn/schema/ovn-ic-sb/encap.go +++ b/internal/server/network/ovn/schema/ovn-ic-sb/encap.go @@ -21,5 +21,5 @@ type Encap struct { GatewayName string `ovsdb:"gateway_name"` IP string `ovsdb:"ip"` Options map[string]string `ovsdb:"options"` - Type EncapType `ovsdb:"type"` + Type EncapType `ovsdb:"type" validate:"oneof='geneve' 'stt' 'vxlan'"` } diff --git a/internal/server/network/ovn/schema/ovn-ic-sb/gateway.go b/internal/server/network/ovn/schema/ovn-ic-sb/gateway.go index fc692042666..c1525e545ae 100644 --- a/internal/server/network/ovn/schema/ovn-ic-sb/gateway.go +++ b/internal/server/network/ovn/schema/ovn-ic-sb/gateway.go @@ -9,7 +9,7 @@ const GatewayTable = "Gateway" type Gateway struct { UUID string `ovsdb:"_uuid"` AvailabilityZone string `ovsdb:"availability_zone"` - Encaps []string `ovsdb:"encaps"` + Encaps []string `ovsdb:"encaps" validate:"min=1"` ExternalIDs map[string]string `ovsdb:"external_ids"` Hostname string `ovsdb:"hostname"` Name string `ovsdb:"name"` diff --git a/internal/server/network/ovn/schema/ovn-ic-sb/model.go b/internal/server/network/ovn/schema/ovn-ic-sb/model.go index 9ad79782e6a..47513325ef9 100644 --- a/internal/server/network/ovn/schema/ovn-ic-sb/model.go +++ b/internal/server/network/ovn/schema/ovn-ic-sb/model.go @@ -6,8 +6,8 @@ package ovsmodel import ( "encoding/json" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // FullDatabaseModel returns the DatabaseModel object to be used in libovsdb diff --git a/internal/server/network/ovn/schema/ovn-ic-sb/port_binding.go b/internal/server/network/ovn/schema/ovn-ic-sb/port_binding.go index 241c8fddf25..1b286b11686 100644 --- a/internal/server/network/ovn/schema/ovn-ic-sb/port_binding.go +++ b/internal/server/network/ovn/schema/ovn-ic-sb/port_binding.go @@ -15,5 +15,5 @@ type PortBinding struct { Gateway string `ovsdb:"gateway"` LogicalPort string `ovsdb:"logical_port"` TransitSwitch string `ovsdb:"transit_switch"` - TunnelKey int `ovsdb:"tunnel_key"` + TunnelKey int `ovsdb:"tunnel_key" validate:"min=1,max=32767"` } diff --git a/internal/server/network/ovn/schema/ovn-ic-sb/route.go b/internal/server/network/ovn/schema/ovn-ic-sb/route.go index 6dafa6ce259..955e2796de7 100644 --- a/internal/server/network/ovn/schema/ovn-ic-sb/route.go +++ b/internal/server/network/ovn/schema/ovn-ic-sb/route.go @@ -21,7 +21,7 @@ type Route struct { ExternalIDs map[string]string `ovsdb:"external_ids"` IPPrefix string `ovsdb:"ip_prefix"` Nexthop string `ovsdb:"nexthop"` - Origin RouteOrigin `ovsdb:"origin"` + Origin RouteOrigin `ovsdb:"origin" validate:"oneof='connected' 'static'"` RouteTable string `ovsdb:"route_table"` TransitSwitch string `ovsdb:"transit_switch"` } diff --git a/internal/server/network/ovn/schema/ovn-nb/acl.go b/internal/server/network/ovn/schema/ovn-nb/acl.go index 85274f394db..ad289db9d4a 100644 --- a/internal/server/network/ovn/schema/ovn-nb/acl.go +++ b/internal/server/network/ovn/schema/ovn-nb/acl.go @@ -29,15 +29,15 @@ var ( // ACL defines an object in ACL table type ACL struct { UUID string `ovsdb:"_uuid"` - Action ACLAction `ovsdb:"action"` - Direction ACLDirection `ovsdb:"direction"` + Action ACLAction `ovsdb:"action" validate:"oneof='allow' 'allow-related' 'allow-stateless' 'drop' 'reject'"` + Direction ACLDirection `ovsdb:"direction" validate:"oneof='from-lport' 'to-lport'"` ExternalIDs map[string]string `ovsdb:"external_ids"` - Label int `ovsdb:"label"` + Label int `ovsdb:"label" validate:"min=0,max=4294967295"` Log bool `ovsdb:"log"` Match string `ovsdb:"match"` Meter *string `ovsdb:"meter"` - Name *string `ovsdb:"name"` + Name *string `ovsdb:"name" validate:"omitempty,max=63"` Options map[string]string `ovsdb:"options"` - Priority int `ovsdb:"priority"` - Severity *ACLSeverity `ovsdb:"severity"` + Priority int `ovsdb:"priority" validate:"min=0,max=32767"` + Severity *ACLSeverity `ovsdb:"severity" validate:"omitempty,oneof='alert' 'warning' 'notice' 'info' 'debug'"` } diff --git a/internal/server/network/ovn/schema/ovn-nb/bfd.go b/internal/server/network/ovn/schema/ovn-nb/bfd.go index 0a7183d7444..dd0b93d396f 100644 --- a/internal/server/network/ovn/schema/ovn-nb/bfd.go +++ b/internal/server/network/ovn/schema/ovn-nb/bfd.go @@ -19,12 +19,12 @@ var ( // BFD defines an object in BFD table type BFD struct { UUID string `ovsdb:"_uuid"` - DetectMult *int `ovsdb:"detect_mult"` + DetectMult *int `ovsdb:"detect_mult" validate:"omitempty,min=1"` DstIP string `ovsdb:"dst_ip"` ExternalIDs map[string]string `ovsdb:"external_ids"` LogicalPort string `ovsdb:"logical_port"` MinRx *int `ovsdb:"min_rx"` - MinTx *int `ovsdb:"min_tx"` + MinTx *int `ovsdb:"min_tx" validate:"omitempty,min=1"` Options map[string]string `ovsdb:"options"` - Status *BFDStatus `ovsdb:"status"` + Status *BFDStatus `ovsdb:"status" validate:"omitempty,oneof='down' 'init' 'up' 'admin_down'"` } diff --git a/internal/server/network/ovn/schema/ovn-nb/connection.go b/internal/server/network/ovn/schema/ovn-nb/connection.go index e0fff1b5d04..c9a1b7690f0 100644 --- a/internal/server/network/ovn/schema/ovn-nb/connection.go +++ b/internal/server/network/ovn/schema/ovn-nb/connection.go @@ -11,7 +11,7 @@ type Connection struct { ExternalIDs map[string]string `ovsdb:"external_ids"` InactivityProbe *int `ovsdb:"inactivity_probe"` IsConnected bool `ovsdb:"is_connected"` - MaxBackoff *int `ovsdb:"max_backoff"` + MaxBackoff *int `ovsdb:"max_backoff" validate:"omitempty,min=1000"` OtherConfig map[string]string `ovsdb:"other_config"` Status map[string]string `ovsdb:"status"` Target string `ovsdb:"target"` diff --git a/internal/server/network/ovn/schema/ovn-nb/forwarding_group.go b/internal/server/network/ovn/schema/ovn-nb/forwarding_group.go index 398e1f1ad2a..77d3ef9c014 100644 --- a/internal/server/network/ovn/schema/ovn-nb/forwarding_group.go +++ b/internal/server/network/ovn/schema/ovn-nb/forwarding_group.go @@ -8,7 +8,7 @@ const ForwardingGroupTable = "Forwarding_Group" // ForwardingGroup defines an object in Forwarding_Group table type ForwardingGroup struct { UUID string `ovsdb:"_uuid"` - ChildPort []string `ovsdb:"child_port"` + ChildPort []string `ovsdb:"child_port" validate:"min=1"` ExternalIDs map[string]string `ovsdb:"external_ids"` Liveness bool `ovsdb:"liveness"` Name string `ovsdb:"name"` diff --git a/internal/server/network/ovn/schema/ovn-nb/gateway_chassis.go b/internal/server/network/ovn/schema/ovn-nb/gateway_chassis.go index 2d9191953f2..de912e29b01 100644 --- a/internal/server/network/ovn/schema/ovn-nb/gateway_chassis.go +++ b/internal/server/network/ovn/schema/ovn-nb/gateway_chassis.go @@ -12,5 +12,5 @@ type GatewayChassis struct { ExternalIDs map[string]string `ovsdb:"external_ids"` Name string `ovsdb:"name"` Options map[string]string `ovsdb:"options"` - Priority int `ovsdb:"priority"` + Priority int `ovsdb:"priority" validate:"min=0,max=32767"` } diff --git a/internal/server/network/ovn/schema/ovn-nb/ha_chassis.go b/internal/server/network/ovn/schema/ovn-nb/ha_chassis.go index 853354f7c0b..9b553175907 100644 --- a/internal/server/network/ovn/schema/ovn-nb/ha_chassis.go +++ b/internal/server/network/ovn/schema/ovn-nb/ha_chassis.go @@ -10,5 +10,5 @@ type HAChassis struct { UUID string `ovsdb:"_uuid"` ChassisName string `ovsdb:"chassis_name"` ExternalIDs map[string]string `ovsdb:"external_ids"` - Priority int `ovsdb:"priority"` + Priority int `ovsdb:"priority" validate:"min=0,max=32767"` } diff --git a/internal/server/network/ovn/schema/ovn-nb/load_balancer.go b/internal/server/network/ovn/schema/ovn-nb/load_balancer.go index dc2d65a7fbb..0f54e63ff65 100644 --- a/internal/server/network/ovn/schema/ovn-nb/load_balancer.go +++ b/internal/server/network/ovn/schema/ovn-nb/load_balancer.go @@ -30,7 +30,7 @@ type LoadBalancer struct { IPPortMappings map[string]string `ovsdb:"ip_port_mappings"` Name string `ovsdb:"name"` Options map[string]string `ovsdb:"options"` - Protocol *LoadBalancerProtocol `ovsdb:"protocol"` - SelectionFields []LoadBalancerSelectionFields `ovsdb:"selection_fields"` + Protocol *LoadBalancerProtocol `ovsdb:"protocol" validate:"omitempty,oneof='tcp' 'udp' 'sctp'"` + SelectionFields []LoadBalancerSelectionFields `ovsdb:"selection_fields" validate:"dive,oneof='eth_src' 'eth_dst' 'ip_src' 'ip_dst' 'tp_src' 'tp_dst'"` Vips map[string]string `ovsdb:"vips"` } diff --git a/internal/server/network/ovn/schema/ovn-nb/logical_router_policy.go b/internal/server/network/ovn/schema/ovn-nb/logical_router_policy.go index e9f7925f089..2c3783fe8c7 100644 --- a/internal/server/network/ovn/schema/ovn-nb/logical_router_policy.go +++ b/internal/server/network/ovn/schema/ovn-nb/logical_router_policy.go @@ -18,11 +18,11 @@ var ( // LogicalRouterPolicy defines an object in Logical_Router_Policy table type LogicalRouterPolicy struct { UUID string `ovsdb:"_uuid"` - Action LogicalRouterPolicyAction `ovsdb:"action"` + Action LogicalRouterPolicyAction `ovsdb:"action" validate:"oneof='allow' 'drop' 'reroute'"` ExternalIDs map[string]string `ovsdb:"external_ids"` Match string `ovsdb:"match"` Nexthop *string `ovsdb:"nexthop"` Nexthops []string `ovsdb:"nexthops"` Options map[string]string `ovsdb:"options"` - Priority int `ovsdb:"priority"` + Priority int `ovsdb:"priority" validate:"min=0,max=32767"` } diff --git a/internal/server/network/ovn/schema/ovn-nb/logical_router_port.go b/internal/server/network/ovn/schema/ovn-nb/logical_router_port.go index f45ffc85dc2..24f38f07a13 100644 --- a/internal/server/network/ovn/schema/ovn-nb/logical_router_port.go +++ b/internal/server/network/ovn/schema/ovn-nb/logical_router_port.go @@ -16,7 +16,7 @@ type LogicalRouterPort struct { Ipv6RaConfigs map[string]string `ovsdb:"ipv6_ra_configs"` MAC string `ovsdb:"mac"` Name string `ovsdb:"name"` - Networks []string `ovsdb:"networks"` + Networks []string `ovsdb:"networks" validate:"min=1"` Options map[string]string `ovsdb:"options"` Peer *string `ovsdb:"peer"` } diff --git a/internal/server/network/ovn/schema/ovn-nb/logical_router_static_route.go b/internal/server/network/ovn/schema/ovn-nb/logical_router_static_route.go index e0ef383229b..14ba4cb8903 100644 --- a/internal/server/network/ovn/schema/ovn-nb/logical_router_static_route.go +++ b/internal/server/network/ovn/schema/ovn-nb/logical_router_static_route.go @@ -23,6 +23,6 @@ type LogicalRouterStaticRoute struct { Nexthop string `ovsdb:"nexthop"` Options map[string]string `ovsdb:"options"` OutputPort *string `ovsdb:"output_port"` - Policy *LogicalRouterStaticRoutePolicy `ovsdb:"policy"` + Policy *LogicalRouterStaticRoutePolicy `ovsdb:"policy" validate:"omitempty,oneof='src-ip' 'dst-ip'"` RouteTable string `ovsdb:"route_table"` } diff --git a/internal/server/network/ovn/schema/ovn-nb/logical_switch_port.go b/internal/server/network/ovn/schema/ovn-nb/logical_switch_port.go index f66312deaed..740e68b17ae 100644 --- a/internal/server/network/ovn/schema/ovn-nb/logical_switch_port.go +++ b/internal/server/network/ovn/schema/ovn-nb/logical_switch_port.go @@ -20,8 +20,8 @@ type LogicalSwitchPort struct { Options map[string]string `ovsdb:"options"` ParentName *string `ovsdb:"parent_name"` PortSecurity []string `ovsdb:"port_security"` - Tag *int `ovsdb:"tag"` - TagRequest *int `ovsdb:"tag_request"` + Tag *int `ovsdb:"tag" validate:"omitempty,min=1,max=4095"` + TagRequest *int `ovsdb:"tag_request" validate:"omitempty,min=0,max=4095"` Type string `ovsdb:"type"` Up *bool `ovsdb:"up"` } diff --git a/internal/server/network/ovn/schema/ovn-nb/meter.go b/internal/server/network/ovn/schema/ovn-nb/meter.go index 236e9a02365..4caa15b6c98 100644 --- a/internal/server/network/ovn/schema/ovn-nb/meter.go +++ b/internal/server/network/ovn/schema/ovn-nb/meter.go @@ -17,9 +17,9 @@ var ( // Meter defines an object in Meter table type Meter struct { UUID string `ovsdb:"_uuid"` - Bands []string `ovsdb:"bands"` + Bands []string `ovsdb:"bands" validate:"min=1"` ExternalIDs map[string]string `ovsdb:"external_ids"` Fair *bool `ovsdb:"fair"` Name string `ovsdb:"name"` - Unit MeterUnit `ovsdb:"unit"` + Unit MeterUnit `ovsdb:"unit" validate:"oneof='kbps' 'pktps'"` } diff --git a/internal/server/network/ovn/schema/ovn-nb/meter_band.go b/internal/server/network/ovn/schema/ovn-nb/meter_band.go index 6723df4e506..85f12f37e9d 100644 --- a/internal/server/network/ovn/schema/ovn-nb/meter_band.go +++ b/internal/server/network/ovn/schema/ovn-nb/meter_band.go @@ -9,13 +9,15 @@ type ( MeterBandAction = string ) -var MeterBandActionDrop MeterBandAction = "drop" +var ( + MeterBandActionDrop MeterBandAction = "drop" +) // MeterBand defines an object in Meter_Band table type MeterBand struct { UUID string `ovsdb:"_uuid"` - Action MeterBandAction `ovsdb:"action"` - BurstSize int `ovsdb:"burst_size"` + Action MeterBandAction `ovsdb:"action" validate:"oneof='drop'"` + BurstSize int `ovsdb:"burst_size" validate:"min=0,max=4294967295"` ExternalIDs map[string]string `ovsdb:"external_ids"` - Rate int `ovsdb:"rate"` + Rate int `ovsdb:"rate" validate:"min=1,max=4294967295"` } diff --git a/internal/server/network/ovn/schema/ovn-nb/mirror.go b/internal/server/network/ovn/schema/ovn-nb/mirror.go index 8d3551ca1b0..cf6fa8099a0 100644 --- a/internal/server/network/ovn/schema/ovn-nb/mirror.go +++ b/internal/server/network/ovn/schema/ovn-nb/mirror.go @@ -21,9 +21,9 @@ var ( type Mirror struct { UUID string `ovsdb:"_uuid"` ExternalIDs map[string]string `ovsdb:"external_ids"` - Filter MirrorFilter `ovsdb:"filter"` + Filter MirrorFilter `ovsdb:"filter" validate:"oneof='from-lport' 'to-lport'"` Index int `ovsdb:"index"` Name string `ovsdb:"name"` Sink string `ovsdb:"sink"` - Type MirrorType `ovsdb:"type"` + Type MirrorType `ovsdb:"type" validate:"oneof='gre' 'erspan'"` } diff --git a/internal/server/network/ovn/schema/ovn-nb/model.go b/internal/server/network/ovn/schema/ovn-nb/model.go index 938639c25bf..a4edd967e20 100644 --- a/internal/server/network/ovn/schema/ovn-nb/model.go +++ b/internal/server/network/ovn/schema/ovn-nb/model.go @@ -6,8 +6,8 @@ package ovsmodel import ( "encoding/json" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // FullDatabaseModel returns the DatabaseModel object to be used in libovsdb diff --git a/internal/server/network/ovn/schema/ovn-nb/nat.go b/internal/server/network/ovn/schema/ovn-nb/nat.go index 9ee78a1e8e0..cb02c563320 100644 --- a/internal/server/network/ovn/schema/ovn-nb/nat.go +++ b/internal/server/network/ovn/schema/ovn-nb/nat.go @@ -28,5 +28,5 @@ type NAT struct { LogicalIP string `ovsdb:"logical_ip"` LogicalPort *string `ovsdb:"logical_port"` Options map[string]string `ovsdb:"options"` - Type NATType `ovsdb:"type"` + Type NATType `ovsdb:"type" validate:"oneof='dnat' 'snat' 'dnat_and_snat'"` } diff --git a/internal/server/network/ovn/schema/ovn-nb/qos.go b/internal/server/network/ovn/schema/ovn-nb/qos.go index dffa414c26e..67a21148763 100644 --- a/internal/server/network/ovn/schema/ovn-nb/qos.go +++ b/internal/server/network/ovn/schema/ovn-nb/qos.go @@ -22,10 +22,10 @@ var ( // QoS defines an object in QoS table type QoS struct { UUID string `ovsdb:"_uuid"` - Action map[string]int `ovsdb:"action"` - Bandwidth map[string]int `ovsdb:"bandwidth"` - Direction QoSDirection `ovsdb:"direction"` + Action map[string]int `ovsdb:"action" validate:"dive,keys,oneof='dscp',endkeys,min=0,max=63"` + Bandwidth map[string]int `ovsdb:"bandwidth" validate:"dive,keys,oneof='rate' 'burst',endkeys,min=1,max=4294967295"` + Direction QoSDirection `ovsdb:"direction" validate:"oneof='from-lport' 'to-lport'"` ExternalIDs map[string]string `ovsdb:"external_ids"` Match string `ovsdb:"match"` - Priority int `ovsdb:"priority"` + Priority int `ovsdb:"priority" validate:"min=0,max=32767"` } diff --git a/internal/server/network/ovn/schema/ovn-sb/bfd.go b/internal/server/network/ovn/schema/ovn-sb/bfd.go index d1ed175a013..769548fca30 100644 --- a/internal/server/network/ovn/schema/ovn-sb/bfd.go +++ b/internal/server/network/ovn/schema/ovn-sb/bfd.go @@ -27,6 +27,6 @@ type BFD struct { MinRx int `ovsdb:"min_rx"` MinTx int `ovsdb:"min_tx"` Options map[string]string `ovsdb:"options"` - SrcPort int `ovsdb:"src_port"` - Status BFDStatus `ovsdb:"status"` + SrcPort int `ovsdb:"src_port" validate:"min=49152,max=65535"` + Status BFDStatus `ovsdb:"status" validate:"oneof='down' 'init' 'up' 'admin_down'"` } diff --git a/internal/server/network/ovn/schema/ovn-sb/chassis.go b/internal/server/network/ovn/schema/ovn-sb/chassis.go index f49dd11b6c9..16afb945440 100644 --- a/internal/server/network/ovn/schema/ovn-sb/chassis.go +++ b/internal/server/network/ovn/schema/ovn-sb/chassis.go @@ -8,7 +8,7 @@ const ChassisTable = "Chassis" // Chassis defines an object in Chassis table type Chassis struct { UUID string `ovsdb:"_uuid"` - Encaps []string `ovsdb:"encaps"` + Encaps []string `ovsdb:"encaps" validate:"min=1"` ExternalIDs map[string]string `ovsdb:"external_ids"` Hostname string `ovsdb:"hostname"` Name string `ovsdb:"name"` diff --git a/internal/server/network/ovn/schema/ovn-sb/connection.go b/internal/server/network/ovn/schema/ovn-sb/connection.go index 9d025f32a4e..ddea49d5762 100644 --- a/internal/server/network/ovn/schema/ovn-sb/connection.go +++ b/internal/server/network/ovn/schema/ovn-sb/connection.go @@ -11,7 +11,7 @@ type Connection struct { ExternalIDs map[string]string `ovsdb:"external_ids"` InactivityProbe *int `ovsdb:"inactivity_probe"` IsConnected bool `ovsdb:"is_connected"` - MaxBackoff *int `ovsdb:"max_backoff"` + MaxBackoff *int `ovsdb:"max_backoff" validate:"omitempty,min=1000"` OtherConfig map[string]string `ovsdb:"other_config"` ReadOnly bool `ovsdb:"read_only"` Role string `ovsdb:"role"` diff --git a/internal/server/network/ovn/schema/ovn-sb/controller_event.go b/internal/server/network/ovn/schema/ovn-sb/controller_event.go index 0ad87d89d9b..cd2e3f5ad19 100644 --- a/internal/server/network/ovn/schema/ovn-sb/controller_event.go +++ b/internal/server/network/ovn/schema/ovn-sb/controller_event.go @@ -9,13 +9,15 @@ type ( ControllerEventEventType = string ) -var ControllerEventEventTypeEmptyLbBackends ControllerEventEventType = "empty_lb_backends" +var ( + ControllerEventEventTypeEmptyLbBackends ControllerEventEventType = "empty_lb_backends" +) // ControllerEvent defines an object in Controller_Event table type ControllerEvent struct { UUID string `ovsdb:"_uuid"` Chassis *string `ovsdb:"chassis"` EventInfo map[string]string `ovsdb:"event_info"` - EventType ControllerEventEventType `ovsdb:"event_type"` + EventType ControllerEventEventType `ovsdb:"event_type" validate:"oneof='empty_lb_backends'"` SeqNum int `ovsdb:"seq_num"` } diff --git a/internal/server/network/ovn/schema/ovn-sb/datapath_binding.go b/internal/server/network/ovn/schema/ovn-sb/datapath_binding.go index ead76e25341..83d7b3dbd88 100644 --- a/internal/server/network/ovn/schema/ovn-sb/datapath_binding.go +++ b/internal/server/network/ovn/schema/ovn-sb/datapath_binding.go @@ -10,5 +10,5 @@ type DatapathBinding struct { UUID string `ovsdb:"_uuid"` ExternalIDs map[string]string `ovsdb:"external_ids"` LoadBalancers []string `ovsdb:"load_balancers"` - TunnelKey int `ovsdb:"tunnel_key"` + TunnelKey int `ovsdb:"tunnel_key" validate:"min=1,max=16777215"` } diff --git a/internal/server/network/ovn/schema/ovn-sb/dhcp_options.go b/internal/server/network/ovn/schema/ovn-sb/dhcp_options.go index 50b1c26a1ad..4e43b9f7456 100644 --- a/internal/server/network/ovn/schema/ovn-sb/dhcp_options.go +++ b/internal/server/network/ovn/schema/ovn-sb/dhcp_options.go @@ -24,7 +24,7 @@ var ( // DHCPOptions defines an object in DHCP_Options table type DHCPOptions struct { UUID string `ovsdb:"_uuid"` - Code int `ovsdb:"code"` + Code int `ovsdb:"code" validate:"min=0,max=254"` Name string `ovsdb:"name"` - Type DHCPOptionsType `ovsdb:"type"` + Type DHCPOptionsType `ovsdb:"type" validate:"oneof='bool' 'uint8' 'uint16' 'uint32' 'ipv4' 'static_routes' 'str' 'host_id' 'domains'"` } diff --git a/internal/server/network/ovn/schema/ovn-sb/dhcpv6_options.go b/internal/server/network/ovn/schema/ovn-sb/dhcpv6_options.go index 08a4763f3da..8dc2ee5433d 100644 --- a/internal/server/network/ovn/schema/ovn-sb/dhcpv6_options.go +++ b/internal/server/network/ovn/schema/ovn-sb/dhcpv6_options.go @@ -18,7 +18,7 @@ var ( // DHCPv6Options defines an object in DHCPv6_Options table type DHCPv6Options struct { UUID string `ovsdb:"_uuid"` - Code int `ovsdb:"code"` + Code int `ovsdb:"code" validate:"min=0,max=254"` Name string `ovsdb:"name"` - Type DHCPv6OptionsType `ovsdb:"type"` + Type DHCPv6OptionsType `ovsdb:"type" validate:"oneof='ipv6' 'str' 'mac'"` } diff --git a/internal/server/network/ovn/schema/ovn-sb/dns.go b/internal/server/network/ovn/schema/ovn-sb/dns.go index 82c18f9aa4c..a567ac0d95d 100644 --- a/internal/server/network/ovn/schema/ovn-sb/dns.go +++ b/internal/server/network/ovn/schema/ovn-sb/dns.go @@ -8,7 +8,7 @@ const DNSTable = "DNS" // DNS defines an object in DNS table type DNS struct { UUID string `ovsdb:"_uuid"` - Datapaths []string `ovsdb:"datapaths"` + Datapaths []string `ovsdb:"datapaths" validate:"min=1"` ExternalIDs map[string]string `ovsdb:"external_ids"` Records map[string]string `ovsdb:"records"` } diff --git a/internal/server/network/ovn/schema/ovn-sb/encap.go b/internal/server/network/ovn/schema/ovn-sb/encap.go index 8d2a1873529..095f95d2709 100644 --- a/internal/server/network/ovn/schema/ovn-sb/encap.go +++ b/internal/server/network/ovn/schema/ovn-sb/encap.go @@ -21,5 +21,5 @@ type Encap struct { ChassisName string `ovsdb:"chassis_name"` IP string `ovsdb:"ip"` Options map[string]string `ovsdb:"options"` - Type EncapType `ovsdb:"type"` + Type EncapType `ovsdb:"type" validate:"oneof='geneve' 'stt' 'vxlan'"` } diff --git a/internal/server/network/ovn/schema/ovn-sb/fdb.go b/internal/server/network/ovn/schema/ovn-sb/fdb.go index fd08635eebb..0a4c8a83a1c 100644 --- a/internal/server/network/ovn/schema/ovn-sb/fdb.go +++ b/internal/server/network/ovn/schema/ovn-sb/fdb.go @@ -8,7 +8,7 @@ const FDBTable = "FDB" // FDB defines an object in FDB table type FDB struct { UUID string `ovsdb:"_uuid"` - DpKey int `ovsdb:"dp_key"` + DpKey int `ovsdb:"dp_key" validate:"min=1,max=16777215"` MAC string `ovsdb:"mac"` - PortKey int `ovsdb:"port_key"` + PortKey int `ovsdb:"port_key" validate:"min=1,max=16777215"` } diff --git a/internal/server/network/ovn/schema/ovn-sb/gateway_chassis.go b/internal/server/network/ovn/schema/ovn-sb/gateway_chassis.go index a7eca3cf267..f5bd851e76c 100644 --- a/internal/server/network/ovn/schema/ovn-sb/gateway_chassis.go +++ b/internal/server/network/ovn/schema/ovn-sb/gateway_chassis.go @@ -12,5 +12,5 @@ type GatewayChassis struct { ExternalIDs map[string]string `ovsdb:"external_ids"` Name string `ovsdb:"name"` Options map[string]string `ovsdb:"options"` - Priority int `ovsdb:"priority"` + Priority int `ovsdb:"priority" validate:"min=0,max=32767"` } diff --git a/internal/server/network/ovn/schema/ovn-sb/ha_chassis.go b/internal/server/network/ovn/schema/ovn-sb/ha_chassis.go index 6c85ac8945c..1f1ec1a194c 100644 --- a/internal/server/network/ovn/schema/ovn-sb/ha_chassis.go +++ b/internal/server/network/ovn/schema/ovn-sb/ha_chassis.go @@ -10,5 +10,5 @@ type HAChassis struct { UUID string `ovsdb:"_uuid"` Chassis *string `ovsdb:"chassis"` ExternalIDs map[string]string `ovsdb:"external_ids"` - Priority int `ovsdb:"priority"` + Priority int `ovsdb:"priority" validate:"min=0,max=32767"` } diff --git a/internal/server/network/ovn/schema/ovn-sb/load_balancer.go b/internal/server/network/ovn/schema/ovn-sb/load_balancer.go index 829c10d2ed9..aace4300d73 100644 --- a/internal/server/network/ovn/schema/ovn-sb/load_balancer.go +++ b/internal/server/network/ovn/schema/ovn-sb/load_balancer.go @@ -23,6 +23,6 @@ type LoadBalancer struct { ExternalIDs map[string]string `ovsdb:"external_ids"` Name string `ovsdb:"name"` Options map[string]string `ovsdb:"options"` - Protocol *LoadBalancerProtocol `ovsdb:"protocol"` + Protocol *LoadBalancerProtocol `ovsdb:"protocol" validate:"omitempty,oneof='tcp' 'udp' 'sctp'"` Vips map[string]string `ovsdb:"vips"` } diff --git a/internal/server/network/ovn/schema/ovn-sb/logical_flow.go b/internal/server/network/ovn/schema/ovn-sb/logical_flow.go index 4e3bdfa62d7..0336d279976 100644 --- a/internal/server/network/ovn/schema/ovn-sb/logical_flow.go +++ b/internal/server/network/ovn/schema/ovn-sb/logical_flow.go @@ -23,8 +23,8 @@ type LogicalFlow struct { LogicalDatapath *string `ovsdb:"logical_datapath"` LogicalDpGroup *string `ovsdb:"logical_dp_group"` Match string `ovsdb:"match"` - Pipeline LogicalFlowPipeline `ovsdb:"pipeline"` - Priority int `ovsdb:"priority"` - TableID int `ovsdb:"table_id"` + Pipeline LogicalFlowPipeline `ovsdb:"pipeline" validate:"oneof='ingress' 'egress'"` + Priority int `ovsdb:"priority" validate:"min=0,max=65535"` + TableID int `ovsdb:"table_id" validate:"min=0,max=32"` Tags map[string]string `ovsdb:"tags"` } diff --git a/internal/server/network/ovn/schema/ovn-sb/meter.go b/internal/server/network/ovn/schema/ovn-sb/meter.go index b45b3cc0bb9..30de038f596 100644 --- a/internal/server/network/ovn/schema/ovn-sb/meter.go +++ b/internal/server/network/ovn/schema/ovn-sb/meter.go @@ -17,7 +17,7 @@ var ( // Meter defines an object in Meter table type Meter struct { UUID string `ovsdb:"_uuid"` - Bands []string `ovsdb:"bands"` + Bands []string `ovsdb:"bands" validate:"min=1"` Name string `ovsdb:"name"` - Unit MeterUnit `ovsdb:"unit"` + Unit MeterUnit `ovsdb:"unit" validate:"oneof='kbps' 'pktps'"` } diff --git a/internal/server/network/ovn/schema/ovn-sb/meter_band.go b/internal/server/network/ovn/schema/ovn-sb/meter_band.go index 6475615230b..614d60307db 100644 --- a/internal/server/network/ovn/schema/ovn-sb/meter_band.go +++ b/internal/server/network/ovn/schema/ovn-sb/meter_band.go @@ -9,12 +9,14 @@ type ( MeterBandAction = string ) -var MeterBandActionDrop MeterBandAction = "drop" +var ( + MeterBandActionDrop MeterBandAction = "drop" +) // MeterBand defines an object in Meter_Band table type MeterBand struct { UUID string `ovsdb:"_uuid"` - Action MeterBandAction `ovsdb:"action"` - BurstSize int `ovsdb:"burst_size"` - Rate int `ovsdb:"rate"` + Action MeterBandAction `ovsdb:"action" validate:"oneof='drop'"` + BurstSize int `ovsdb:"burst_size" validate:"min=0,max=4294967295"` + Rate int `ovsdb:"rate" validate:"min=1,max=4294967295"` } diff --git a/internal/server/network/ovn/schema/ovn-sb/mirror.go b/internal/server/network/ovn/schema/ovn-sb/mirror.go index 8d3551ca1b0..cf6fa8099a0 100644 --- a/internal/server/network/ovn/schema/ovn-sb/mirror.go +++ b/internal/server/network/ovn/schema/ovn-sb/mirror.go @@ -21,9 +21,9 @@ var ( type Mirror struct { UUID string `ovsdb:"_uuid"` ExternalIDs map[string]string `ovsdb:"external_ids"` - Filter MirrorFilter `ovsdb:"filter"` + Filter MirrorFilter `ovsdb:"filter" validate:"oneof='from-lport' 'to-lport'"` Index int `ovsdb:"index"` Name string `ovsdb:"name"` Sink string `ovsdb:"sink"` - Type MirrorType `ovsdb:"type"` + Type MirrorType `ovsdb:"type" validate:"oneof='gre' 'erspan'"` } diff --git a/internal/server/network/ovn/schema/ovn-sb/model.go b/internal/server/network/ovn/schema/ovn-sb/model.go index d69ef827b9f..f0e652633a2 100644 --- a/internal/server/network/ovn/schema/ovn-sb/model.go +++ b/internal/server/network/ovn/schema/ovn-sb/model.go @@ -6,8 +6,8 @@ package ovsmodel import ( "encoding/json" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // FullDatabaseModel returns the DatabaseModel object to be used in libovsdb diff --git a/internal/server/network/ovn/schema/ovn-sb/multicast_group.go b/internal/server/network/ovn/schema/ovn-sb/multicast_group.go index 02b0ead2789..1fd3fb32b61 100644 --- a/internal/server/network/ovn/schema/ovn-sb/multicast_group.go +++ b/internal/server/network/ovn/schema/ovn-sb/multicast_group.go @@ -11,5 +11,5 @@ type MulticastGroup struct { Datapath string `ovsdb:"datapath"` Name string `ovsdb:"name"` Ports []string `ovsdb:"ports"` - TunnelKey int `ovsdb:"tunnel_key"` + TunnelKey int `ovsdb:"tunnel_key" validate:"min=32768,max=65535"` } diff --git a/internal/server/network/ovn/schema/ovn-sb/port_binding.go b/internal/server/network/ovn/schema/ovn-sb/port_binding.go index fe9d0bf508b..1f379369b05 100644 --- a/internal/server/network/ovn/schema/ovn-sb/port_binding.go +++ b/internal/server/network/ovn/schema/ovn-sb/port_binding.go @@ -25,8 +25,8 @@ type PortBinding struct { PortSecurity []string `ovsdb:"port_security"` RequestedAdditionalChassis []string `ovsdb:"requested_additional_chassis"` RequestedChassis *string `ovsdb:"requested_chassis"` - Tag *int `ovsdb:"tag"` - TunnelKey int `ovsdb:"tunnel_key"` + Tag *int `ovsdb:"tag" validate:"omitempty,min=1,max=4095"` + TunnelKey int `ovsdb:"tunnel_key" validate:"min=1,max=32767"` Type string `ovsdb:"type"` Up *bool `ovsdb:"up"` VirtualParent *string `ovsdb:"virtual_parent"` diff --git a/internal/server/network/ovn/schema/ovn-sb/service_monitor.go b/internal/server/network/ovn/schema/ovn-sb/service_monitor.go index c40c36ba810..b22391af030 100644 --- a/internal/server/network/ovn/schema/ovn-sb/service_monitor.go +++ b/internal/server/network/ovn/schema/ovn-sb/service_monitor.go @@ -25,9 +25,9 @@ type ServiceMonitor struct { IP string `ovsdb:"ip"` LogicalPort string `ovsdb:"logical_port"` Options map[string]string `ovsdb:"options"` - Port int `ovsdb:"port"` - Protocol *ServiceMonitorProtocol `ovsdb:"protocol"` + Port int `ovsdb:"port" validate:"min=0,max=65535"` + Protocol *ServiceMonitorProtocol `ovsdb:"protocol" validate:"omitempty,oneof='tcp' 'udp'"` SrcIP string `ovsdb:"src_ip"` SrcMAC string `ovsdb:"src_mac"` - Status *ServiceMonitorStatus `ovsdb:"status"` + Status *ServiceMonitorStatus `ovsdb:"status" validate:"omitempty,oneof='online' 'offline' 'error'"` } diff --git a/internal/server/network/ovs/errors.go b/internal/server/network/ovs/errors.go index 17e58aea774..929f79ac622 100644 --- a/internal/server/network/ovs/errors.go +++ b/internal/server/network/ovs/errors.go @@ -3,7 +3,7 @@ package ovs import ( "errors" - ovsdbClient "github.com/ovn-org/libovsdb/client" + ovsdbClient "github.com/ovn-kubernetes/libovsdb/client" ) // ErrExists indicates that a DB record already exists. diff --git a/internal/server/network/ovs/ovs.go b/internal/server/network/ovs/ovs.go index 653f9bd6f2d..1a2692940d5 100644 --- a/internal/server/network/ovs/ovs.go +++ b/internal/server/network/ovs/ovs.go @@ -8,7 +8,7 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/go-logr/logr" - ovsdbClient "github.com/ovn-org/libovsdb/client" + ovsdbClient "github.com/ovn-kubernetes/libovsdb/client" ovsSwitch "github.com/lxc/incus/v6/internal/server/network/ovs/schema/ovs" ) diff --git a/internal/server/network/ovs/ovs_actions.go b/internal/server/network/ovs/ovs_actions.go index e9930ce2d3f..889cb857edb 100644 --- a/internal/server/network/ovs/ovs_actions.go +++ b/internal/server/network/ovs/ovs_actions.go @@ -10,9 +10,9 @@ import ( "sync" "time" - ovsdbClient "github.com/ovn-org/libovsdb/client" - ovsdbModel "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + ovsdbClient "github.com/ovn-kubernetes/libovsdb/client" + ovsdbModel "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" "github.com/lxc/incus/v6/internal/server/ip" ovsSwitch "github.com/lxc/incus/v6/internal/server/network/ovs/schema/ovs" diff --git a/internal/server/network/ovs/schema/ovs/autoattach.go b/internal/server/network/ovs/schema/ovs/autoattach.go index 80bb963b4fb..684c21046ec 100644 --- a/internal/server/network/ovs/schema/ovs/autoattach.go +++ b/internal/server/network/ovs/schema/ovs/autoattach.go @@ -8,7 +8,7 @@ const AutoAttachTable = "AutoAttach" // AutoAttach defines an object in AutoAttach table type AutoAttach struct { UUID string `ovsdb:"_uuid"` - Mappings map[int]int `ovsdb:"mappings"` + Mappings map[int]int `ovsdb:"mappings" validate:"dive,keys,min=0,max=16777215,endkeys,min=0,max=4095"` SystemDescription string `ovsdb:"system_description"` SystemName string `ovsdb:"system_name"` } diff --git a/internal/server/network/ovs/schema/ovs/bridge.go b/internal/server/network/ovs/schema/ovs/bridge.go index c7648d07277..734acc40a15 100644 --- a/internal/server/network/ovs/schema/ovs/bridge.go +++ b/internal/server/network/ovs/schema/ovs/bridge.go @@ -30,9 +30,9 @@ type Bridge struct { DatapathType string `ovsdb:"datapath_type"` DatapathVersion string `ovsdb:"datapath_version"` ExternalIDs map[string]string `ovsdb:"external_ids"` - FailMode *BridgeFailMode `ovsdb:"fail_mode"` - FloodVLANs []int `ovsdb:"flood_vlans"` - FlowTables map[int]string `ovsdb:"flow_tables"` + FailMode *BridgeFailMode `ovsdb:"fail_mode" validate:"omitempty,oneof='standalone' 'secure'"` + FloodVLANs []int `ovsdb:"flood_vlans" validate:"max=4096,dive,min=0,max=4095"` + FlowTables map[int]string `ovsdb:"flow_tables" validate:"dive,keys,min=0,max=254"` IPFIX *string `ovsdb:"ipfix"` McastSnoopingEnable bool `ovsdb:"mcast_snooping_enable"` Mirrors []string `ovsdb:"mirrors"` @@ -40,7 +40,7 @@ type Bridge struct { Netflow *string `ovsdb:"netflow"` OtherConfig map[string]string `ovsdb:"other_config"` Ports []string `ovsdb:"ports"` - Protocols []BridgeProtocols `ovsdb:"protocols"` + Protocols []BridgeProtocols `ovsdb:"protocols" validate:"dive,oneof='OpenFlow10' 'OpenFlow11' 'OpenFlow12' 'OpenFlow13' 'OpenFlow14' 'OpenFlow15'"` RSTPEnable bool `ovsdb:"rstp_enable"` RSTPStatus map[string]string `ovsdb:"rstp_status"` Sflow *string `ovsdb:"sflow"` diff --git a/internal/server/network/ovs/schema/ovs/controller.go b/internal/server/network/ovs/schema/ovs/controller.go index c9b75a17e08..53777c58607 100644 --- a/internal/server/network/ovs/schema/ovs/controller.go +++ b/internal/server/network/ovs/schema/ovs/controller.go @@ -24,10 +24,10 @@ var ( // Controller defines an object in Controller table type Controller struct { UUID string `ovsdb:"_uuid"` - ConnectionMode *ControllerConnectionMode `ovsdb:"connection_mode"` - ControllerBurstLimit *int `ovsdb:"controller_burst_limit"` - ControllerQueueSize *int `ovsdb:"controller_queue_size"` - ControllerRateLimit *int `ovsdb:"controller_rate_limit"` + ConnectionMode *ControllerConnectionMode `ovsdb:"connection_mode" validate:"omitempty,oneof='in-band' 'out-of-band'"` + ControllerBurstLimit *int `ovsdb:"controller_burst_limit" validate:"omitempty,min=25"` + ControllerQueueSize *int `ovsdb:"controller_queue_size" validate:"omitempty,min=1,max=512"` + ControllerRateLimit *int `ovsdb:"controller_rate_limit" validate:"omitempty,min=100"` EnableAsyncMessages *bool `ovsdb:"enable_async_messages"` ExternalIDs map[string]string `ovsdb:"external_ids"` InactivityProbe *int `ovsdb:"inactivity_probe"` @@ -35,10 +35,10 @@ type Controller struct { LocalGateway *string `ovsdb:"local_gateway"` LocalIP *string `ovsdb:"local_ip"` LocalNetmask *string `ovsdb:"local_netmask"` - MaxBackoff *int `ovsdb:"max_backoff"` + MaxBackoff *int `ovsdb:"max_backoff" validate:"omitempty,min=1000"` OtherConfig map[string]string `ovsdb:"other_config"` - Role *ControllerRole `ovsdb:"role"` + Role *ControllerRole `ovsdb:"role" validate:"omitempty,oneof='other' 'master' 'slave'"` Status map[string]string `ovsdb:"status"` Target string `ovsdb:"target"` - Type *ControllerType `ovsdb:"type"` + Type *ControllerType `ovsdb:"type" validate:"omitempty,oneof='primary' 'service'"` } diff --git a/internal/server/network/ovs/schema/ovs/ct_timeout_policy.go b/internal/server/network/ovs/schema/ovs/ct_timeout_policy.go index 7b3e46f0353..577a3961222 100644 --- a/internal/server/network/ovs/schema/ovs/ct_timeout_policy.go +++ b/internal/server/network/ovs/schema/ovs/ct_timeout_policy.go @@ -32,5 +32,5 @@ var ( type CTTimeoutPolicy struct { UUID string `ovsdb:"_uuid"` ExternalIDs map[string]string `ovsdb:"external_ids"` - Timeouts map[string]int `ovsdb:"timeouts"` + Timeouts map[string]int `ovsdb:"timeouts" validate:"dive,keys,oneof='tcp_syn_sent' 'tcp_syn_recv' 'tcp_established' 'tcp_fin_wait' 'tcp_close_wait' 'tcp_last_ack' 'tcp_time_wait' 'tcp_close' 'tcp_syn_sent2' 'tcp_retransmit' 'tcp_unack' 'udp_first' 'udp_single' 'udp_multiple' 'icmp_first' 'icmp_reply',endkeys,min=0,max=4294967295"` } diff --git a/internal/server/network/ovs/schema/ovs/datapath.go b/internal/server/network/ovs/schema/ovs/datapath.go index ca46a6c0fb6..a3d114fe86e 100644 --- a/internal/server/network/ovs/schema/ovs/datapath.go +++ b/internal/server/network/ovs/schema/ovs/datapath.go @@ -9,7 +9,7 @@ const DatapathTable = "Datapath" type Datapath struct { UUID string `ovsdb:"_uuid"` Capabilities map[string]string `ovsdb:"capabilities"` - CTZones map[int]string `ovsdb:"ct_zones"` + CTZones map[int]string `ovsdb:"ct_zones" validate:"dive,keys,min=0,max=65535"` DatapathVersion string `ovsdb:"datapath_version"` ExternalIDs map[string]string `ovsdb:"external_ids"` } diff --git a/internal/server/network/ovs/schema/ovs/flow_sample_collector_set.go b/internal/server/network/ovs/schema/ovs/flow_sample_collector_set.go index ae6c338a8ee..15303d361a5 100644 --- a/internal/server/network/ovs/schema/ovs/flow_sample_collector_set.go +++ b/internal/server/network/ovs/schema/ovs/flow_sample_collector_set.go @@ -10,6 +10,6 @@ type FlowSampleCollectorSet struct { UUID string `ovsdb:"_uuid"` Bridge string `ovsdb:"bridge"` ExternalIDs map[string]string `ovsdb:"external_ids"` - ID int `ovsdb:"id"` + ID int `ovsdb:"id" validate:"min=0,max=4294967295"` IPFIX *string `ovsdb:"ipfix"` } diff --git a/internal/server/network/ovs/schema/ovs/flow_table.go b/internal/server/network/ovs/schema/ovs/flow_table.go index 84f8599c589..1f43c5208b6 100644 --- a/internal/server/network/ovs/schema/ovs/flow_table.go +++ b/internal/server/network/ovs/schema/ovs/flow_table.go @@ -18,9 +18,9 @@ var ( type FlowTable struct { UUID string `ovsdb:"_uuid"` ExternalIDs map[string]string `ovsdb:"external_ids"` - FlowLimit *int `ovsdb:"flow_limit"` + FlowLimit *int `ovsdb:"flow_limit" validate:"omitempty,min=0"` Groups []string `ovsdb:"groups"` Name *string `ovsdb:"name"` - OverflowPolicy *FlowTableOverflowPolicy `ovsdb:"overflow_policy"` - Prefixes []string `ovsdb:"prefixes"` + OverflowPolicy *FlowTableOverflowPolicy `ovsdb:"overflow_policy" validate:"omitempty,oneof='refuse' 'evict'"` + Prefixes []string `ovsdb:"prefixes" validate:"max=3"` } diff --git a/internal/server/network/ovs/schema/ovs/interface.go b/internal/server/network/ovs/schema/ovs/interface.go index 78f6082fa9b..a57acd4eeac 100644 --- a/internal/server/network/ovs/schema/ovs/interface.go +++ b/internal/server/network/ovs/schema/ovs/interface.go @@ -26,34 +26,34 @@ var ( // Interface defines an object in Interface table type Interface struct { UUID string `ovsdb:"_uuid"` - AdminState *InterfaceAdminState `ovsdb:"admin_state"` + AdminState *InterfaceAdminState `ovsdb:"admin_state" validate:"omitempty,oneof='up' 'down'"` BFD map[string]string `ovsdb:"bfd"` BFDStatus map[string]string `ovsdb:"bfd_status"` CFMFault *bool `ovsdb:"cfm_fault"` CFMFaultStatus []string `ovsdb:"cfm_fault_status"` CFMFlapCount *int `ovsdb:"cfm_flap_count"` - CFMHealth *int `ovsdb:"cfm_health"` + CFMHealth *int `ovsdb:"cfm_health" validate:"omitempty,min=0,max=100"` CFMMpid *int `ovsdb:"cfm_mpid"` CFMRemoteMpids []int `ovsdb:"cfm_remote_mpids"` - CFMRemoteOpstate *InterfaceCFMRemoteOpstate `ovsdb:"cfm_remote_opstate"` - Duplex *InterfaceDuplex `ovsdb:"duplex"` + CFMRemoteOpstate *InterfaceCFMRemoteOpstate `ovsdb:"cfm_remote_opstate" validate:"omitempty,oneof='up' 'down'"` + Duplex *InterfaceDuplex `ovsdb:"duplex" validate:"omitempty,oneof='half' 'full'"` Error *string `ovsdb:"error"` ExternalIDs map[string]string `ovsdb:"external_ids"` - Ifindex *int `ovsdb:"ifindex"` - IngressPolicingBurst int `ovsdb:"ingress_policing_burst"` - IngressPolicingRate int `ovsdb:"ingress_policing_rate"` + Ifindex *int `ovsdb:"ifindex" validate:"omitempty,min=0,max=4294967295"` + IngressPolicingBurst int `ovsdb:"ingress_policing_burst" validate:"min=0"` + IngressPolicingRate int `ovsdb:"ingress_policing_rate" validate:"min=0"` LACPCurrent *bool `ovsdb:"lacp_current"` LinkResets *int `ovsdb:"link_resets"` LinkSpeed *int `ovsdb:"link_speed"` - LinkState *InterfaceLinkState `ovsdb:"link_state"` + LinkState *InterfaceLinkState `ovsdb:"link_state" validate:"omitempty,oneof='up' 'down'"` LLDP map[string]string `ovsdb:"lldp"` MAC *string `ovsdb:"mac"` MACInUse *string `ovsdb:"mac_in_use"` MTU *int `ovsdb:"mtu"` - MTURequest *int `ovsdb:"mtu_request"` + MTURequest *int `ovsdb:"mtu_request" validate:"omitempty,min=1"` Name string `ovsdb:"name"` Ofport *int `ovsdb:"ofport"` - OfportRequest *int `ovsdb:"ofport_request"` + OfportRequest *int `ovsdb:"ofport_request" validate:"omitempty,min=1,max=65279"` Options map[string]string `ovsdb:"options"` OtherConfig map[string]string `ovsdb:"other_config"` Statistics map[string]int `ovsdb:"statistics"` diff --git a/internal/server/network/ovs/schema/ovs/ipfix.go b/internal/server/network/ovs/schema/ovs/ipfix.go index e272d7ec35b..340087a2a87 100644 --- a/internal/server/network/ovs/schema/ovs/ipfix.go +++ b/internal/server/network/ovs/schema/ovs/ipfix.go @@ -8,12 +8,12 @@ const IPFIXTable = "IPFIX" // IPFIX defines an object in IPFIX table type IPFIX struct { UUID string `ovsdb:"_uuid"` - CacheActiveTimeout *int `ovsdb:"cache_active_timeout"` - CacheMaxFlows *int `ovsdb:"cache_max_flows"` + CacheActiveTimeout *int `ovsdb:"cache_active_timeout" validate:"omitempty,min=0,max=4200"` + CacheMaxFlows *int `ovsdb:"cache_max_flows" validate:"omitempty,min=0,max=4294967295"` ExternalIDs map[string]string `ovsdb:"external_ids"` - ObsDomainID *int `ovsdb:"obs_domain_id"` - ObsPointID *int `ovsdb:"obs_point_id"` + ObsDomainID *int `ovsdb:"obs_domain_id" validate:"omitempty,min=0,max=4294967295"` + ObsPointID *int `ovsdb:"obs_point_id" validate:"omitempty,min=0,max=4294967295"` OtherConfig map[string]string `ovsdb:"other_config"` - Sampling *int `ovsdb:"sampling"` + Sampling *int `ovsdb:"sampling" validate:"omitempty,min=1,max=4294967295"` Targets []string `ovsdb:"targets"` } diff --git a/internal/server/network/ovs/schema/ovs/manager.go b/internal/server/network/ovs/schema/ovs/manager.go index 901c2207b71..a6b0eff06a5 100644 --- a/internal/server/network/ovs/schema/ovs/manager.go +++ b/internal/server/network/ovs/schema/ovs/manager.go @@ -17,11 +17,11 @@ var ( // Manager defines an object in Manager table type Manager struct { UUID string `ovsdb:"_uuid"` - ConnectionMode *ManagerConnectionMode `ovsdb:"connection_mode"` + ConnectionMode *ManagerConnectionMode `ovsdb:"connection_mode" validate:"omitempty,oneof='in-band' 'out-of-band'"` ExternalIDs map[string]string `ovsdb:"external_ids"` InactivityProbe *int `ovsdb:"inactivity_probe"` IsConnected bool `ovsdb:"is_connected"` - MaxBackoff *int `ovsdb:"max_backoff"` + MaxBackoff *int `ovsdb:"max_backoff" validate:"omitempty,min=1000"` OtherConfig map[string]string `ovsdb:"other_config"` Status map[string]string `ovsdb:"status"` Target string `ovsdb:"target"` diff --git a/internal/server/network/ovs/schema/ovs/mirror.go b/internal/server/network/ovs/schema/ovs/mirror.go index 059cd4e3628..bbc2dd8e442 100644 --- a/internal/server/network/ovs/schema/ovs/mirror.go +++ b/internal/server/network/ovs/schema/ovs/mirror.go @@ -11,11 +11,11 @@ type Mirror struct { ExternalIDs map[string]string `ovsdb:"external_ids"` Name string `ovsdb:"name"` OutputPort *string `ovsdb:"output_port"` - OutputVLAN *int `ovsdb:"output_vlan"` + OutputVLAN *int `ovsdb:"output_vlan" validate:"omitempty,min=1,max=4095"` SelectAll bool `ovsdb:"select_all"` SelectDstPort []string `ovsdb:"select_dst_port"` SelectSrcPort []string `ovsdb:"select_src_port"` - SelectVLAN []int `ovsdb:"select_vlan"` - Snaplen *int `ovsdb:"snaplen"` + SelectVLAN []int `ovsdb:"select_vlan" validate:"max=4096,dive,min=0,max=4095"` + Snaplen *int `ovsdb:"snaplen" validate:"omitempty,min=14,max=65535"` Statistics map[string]int `ovsdb:"statistics"` } diff --git a/internal/server/network/ovs/schema/ovs/model.go b/internal/server/network/ovs/schema/ovs/model.go index 032e8e0642a..68273777484 100644 --- a/internal/server/network/ovs/schema/ovs/model.go +++ b/internal/server/network/ovs/schema/ovs/model.go @@ -6,8 +6,8 @@ package ovsmodel import ( "encoding/json" - "github.com/ovn-org/libovsdb/model" - "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-kubernetes/libovsdb/model" + "github.com/ovn-kubernetes/libovsdb/ovsdb" ) // FullDatabaseModel returns the DatabaseModel object to be used in libovsdb diff --git a/internal/server/network/ovs/schema/ovs/netflow.go b/internal/server/network/ovs/schema/ovs/netflow.go index 27eb9e716fd..2eb7bc06b1c 100644 --- a/internal/server/network/ovs/schema/ovs/netflow.go +++ b/internal/server/network/ovs/schema/ovs/netflow.go @@ -8,10 +8,10 @@ const NetFlowTable = "NetFlow" // NetFlow defines an object in NetFlow table type NetFlow struct { UUID string `ovsdb:"_uuid"` - ActiveTimeout int `ovsdb:"active_timeout"` + ActiveTimeout int `ovsdb:"active_timeout" validate:"min=-1"` AddIDToInterface bool `ovsdb:"add_id_to_interface"` - EngineID *int `ovsdb:"engine_id"` - EngineType *int `ovsdb:"engine_type"` + EngineID *int `ovsdb:"engine_id" validate:"omitempty,min=0,max=255"` + EngineType *int `ovsdb:"engine_type" validate:"omitempty,min=0,max=255"` ExternalIDs map[string]string `ovsdb:"external_ids"` - Targets []string `ovsdb:"targets"` + Targets []string `ovsdb:"targets" validate:"min=1"` } diff --git a/internal/server/network/ovs/schema/ovs/port.go b/internal/server/network/ovs/schema/ovs/port.go index c77ee67c2f3..12d61d8f238 100644 --- a/internal/server/network/ovs/schema/ovs/port.go +++ b/internal/server/network/ovs/schema/ovs/port.go @@ -31,13 +31,13 @@ type Port struct { BondActiveSlave *string `ovsdb:"bond_active_slave"` BondDowndelay int `ovsdb:"bond_downdelay"` BondFakeIface bool `ovsdb:"bond_fake_iface"` - BondMode *PortBondMode `ovsdb:"bond_mode"` + BondMode *PortBondMode `ovsdb:"bond_mode" validate:"omitempty,oneof='balance-tcp' 'balance-slb' 'active-backup'"` BondUpdelay int `ovsdb:"bond_updelay"` - CVLANs []int `ovsdb:"cvlans"` + CVLANs []int `ovsdb:"cvlans" validate:"max=4096,dive,min=0,max=4095"` ExternalIDs map[string]string `ovsdb:"external_ids"` FakeBridge bool `ovsdb:"fake_bridge"` - Interfaces []string `ovsdb:"interfaces"` - LACP *PortLACP `ovsdb:"lacp"` + Interfaces []string `ovsdb:"interfaces" validate:"min=1"` + LACP *PortLACP `ovsdb:"lacp" validate:"omitempty,oneof='active' 'passive' 'off'"` MAC *string `ovsdb:"mac"` Name string `ovsdb:"name"` OtherConfig map[string]string `ovsdb:"other_config"` @@ -47,7 +47,7 @@ type Port struct { RSTPStatus map[string]string `ovsdb:"rstp_status"` Statistics map[string]int `ovsdb:"statistics"` Status map[string]string `ovsdb:"status"` - Tag *int `ovsdb:"tag"` - Trunks []int `ovsdb:"trunks"` - VLANMode *PortVLANMode `ovsdb:"vlan_mode"` + Tag *int `ovsdb:"tag" validate:"omitempty,min=0,max=4095"` + Trunks []int `ovsdb:"trunks" validate:"max=4096,dive,min=0,max=4095"` + VLANMode *PortVLANMode `ovsdb:"vlan_mode" validate:"omitempty,oneof='trunk' 'access' 'native-tagged' 'native-untagged' 'dot1q-tunnel'"` } diff --git a/internal/server/network/ovs/schema/ovs/qos.go b/internal/server/network/ovs/schema/ovs/qos.go index 656abf67e5a..3759665d676 100644 --- a/internal/server/network/ovs/schema/ovs/qos.go +++ b/internal/server/network/ovs/schema/ovs/qos.go @@ -10,6 +10,6 @@ type QoS struct { UUID string `ovsdb:"_uuid"` ExternalIDs map[string]string `ovsdb:"external_ids"` OtherConfig map[string]string `ovsdb:"other_config"` - Queues map[int]string `ovsdb:"queues"` + Queues map[int]string `ovsdb:"queues" validate:"dive,keys,min=0,max=4294967295"` Type string `ovsdb:"type"` } diff --git a/internal/server/network/ovs/schema/ovs/queue.go b/internal/server/network/ovs/schema/ovs/queue.go index e67f399bdae..6775ab9a8a2 100644 --- a/internal/server/network/ovs/schema/ovs/queue.go +++ b/internal/server/network/ovs/schema/ovs/queue.go @@ -8,7 +8,7 @@ const QueueTable = "Queue" // Queue defines an object in Queue table type Queue struct { UUID string `ovsdb:"_uuid"` - DSCP *int `ovsdb:"dscp"` + DSCP *int `ovsdb:"dscp" validate:"omitempty,min=0,max=63"` ExternalIDs map[string]string `ovsdb:"external_ids"` OtherConfig map[string]string `ovsdb:"other_config"` } diff --git a/internal/server/network/ovs/schema/ovs/sflow.go b/internal/server/network/ovs/schema/ovs/sflow.go index c7a7a6d7cee..11a756fedad 100644 --- a/internal/server/network/ovs/schema/ovs/sflow.go +++ b/internal/server/network/ovs/schema/ovs/sflow.go @@ -13,5 +13,5 @@ type SFlow struct { Header *int `ovsdb:"header"` Polling *int `ovsdb:"polling"` Sampling *int `ovsdb:"sampling"` - Targets []string `ovsdb:"targets"` + Targets []string `ovsdb:"targets" validate:"min=1"` } diff --git a/internal/version/api.go b/internal/version/api.go index a02c677f21a..78e850e369c 100644 --- a/internal/version/api.go +++ b/internal/version/api.go @@ -501,6 +501,7 @@ var APIExtensions = []string{ "init_preseed_cluster_groups", "usb_attached", "backup_iso", + "instance_systemd_credentials", } // APIExtensionsCount returns the number of available API extensions. diff --git a/shared/resources/resources.go b/shared/resources/resources.go index 68c0eb31ed0..47d17b4f084 100644 --- a/shared/resources/resources.go +++ b/shared/resources/resources.go @@ -24,7 +24,7 @@ func GetResources() (*api.Resources, error) { defer muResources.Unlock() // Check if we ran less than 10s ago. - if lastResources != nil && lastRun.Add(10*time.Second).Before(time.Now()) { + if lastResources != nil && lastRun.Add(10*time.Second).After(time.Now()) { return lastResources, nil } diff --git a/shared/validate/validate.go b/shared/validate/validate.go index fb8d3546b29..9f6dbe03474 100644 --- a/shared/validate/validate.go +++ b/shared/validate/validate.go @@ -2,6 +2,7 @@ package validate import ( "bytes" + "encoding/base64" "errors" "fmt" "net" @@ -942,3 +943,13 @@ func IsMinimumDuration(minimum time.Duration) func(value string) error { return nil } } + +// IsBase64 validates whether the string is Base64 encoded. +func IsBase64(value string) error { + _, err := base64.RawStdEncoding.DecodeString(strings.TrimRight(value, "=")) + if err != nil { + return fmt.Errorf("Invalid value for a base64 string %q: %w", value, err) + } + + return nil +} diff --git a/test/main.sh b/test/main.sh index 0aa8adefa38..b7ed91b6085 100755 --- a/test/main.sh +++ b/test/main.sh @@ -391,6 +391,7 @@ if [ "${1:-"all"}" != "cluster" ]; then run_test test_storage_volume_recover "Recover storage volumes" run_test test_syslog_socket "Syslog socket" run_test test_incus_user "incus-user" + run_test test_systemd "systemd" fi # shellcheck disable=SC2034 diff --git a/test/suites/storage_snapshots.sh b/test/suites/storage_snapshots.sh index 2d09dc57a27..87f97b1bc3b 100644 --- a/test/suites/storage_snapshots.sh +++ b/test/suites/storage_snapshots.sh @@ -243,6 +243,10 @@ test_storage_volume_snapshots() { incus storage volume delete "${storage_pool}" "vol1" incus project delete "project1" + + # FIXME: LINSTOR requires this sleep for now. + sleep 5 + incus storage delete "${storage_pool}" incus remote remove "test" diff --git a/test/suites/systemd.sh b/test/suites/systemd.sh new file mode 100644 index 00000000000..e3c23bc1c44 --- /dev/null +++ b/test/suites/systemd.sh @@ -0,0 +1,63 @@ +assert_systemd_credentials_entries() { + entries=$(incus exec foo -- ls -A1 /dev/.incus-systemd-credentials | wc -l) + if [ "$entries" != "$1" ]; then + printf "Expected %s entries in systemd credentials directory; got %s\n" "$1" "$entries" + false + fi +} + +assert_systemd_credentials_value() { + value=$(incus exec foo cat "/dev/.incus-systemd-credentials/$1") + if [ "$value" != "$2" ]; then + printf "Expected %s for systemd credential %s; got %s\n" "$2" "$1" "$value" + false + fi +} + +test_systemd() { + ensure_import_testimage + ensure_has_localhost_remote "${INCUS_ADDR}" + + incus launch testimage foo + stat=$(incus exec foo -- stat -c%a /dev/.incus-systemd-credentials) + if [ "$stat" != "100" ]; then + echo "Wrong permissions on systemd credentials directory" + false + fi + + assert_systemd_credentials_entries 0 + + # Regular credential + incus config set foo systemd.credential.foo bar + assert_systemd_credentials_entries 1 + assert_systemd_credentials_value foo bar + + # Base64 credential + incus config set foo systemd.credential-binary.xxx eXl5 + assert_systemd_credentials_entries 2 + assert_systemd_credentials_value foo bar + assert_systemd_credentials_value xxx yyy + + # Mutually exclusive credential and credential-binary keys + ! incus config set foo systemd.credential-binary.foo YmF6========= || false + incus config unset foo systemd.credential.foo + + # Base64 credential with superfluous padding + incus config set foo systemd.credential-binary.foo YmF6========= + assert_systemd_credentials_entries 2 + assert_systemd_credentials_value foo baz + assert_systemd_credentials_value xxx yyy + + # Consistency after reboot + incus restart foo --force + assert_systemd_credentials_entries 2 + assert_systemd_credentials_value foo baz + assert_systemd_credentials_value xxx yyy + + # Credential deletion + incus config unset foo systemd.credential-binary.foo + assert_systemd_credentials_entries 1 + assert_systemd_credentials_value xxx yyy + + incus rm -f foo +}