treewide: introduce osbase package and move things around
All except localregistry moved from metropolis/pkg to osbase,
localregistry moved to metropolis/test as its only used there anyway.
Change-Id: If1a4bf377364bef0ac23169e1b90379c71b06d72
Reviewed-on: https://review.monogon.dev/c/monogon/+/3079
Tested-by: Jenkins CI
Reviewed-by: Serge Bazanski <serge@monogon.tech>
diff --git a/metropolis/test/e2e/BUILD.bazel b/metropolis/test/e2e/BUILD.bazel
index cb8645b..565223e 100644
--- a/metropolis/test/e2e/BUILD.bazel
+++ b/metropolis/test/e2e/BUILD.bazel
@@ -1,4 +1,4 @@
-load("//metropolis/pkg/localregistry:def.bzl", "localregistry_manifest")
+load("//metropolis/test/localregistry:def.bzl", "localregistry_manifest")
localregistry_manifest(
name = "testimages_manifest",
diff --git a/metropolis/test/e2e/k8s_cts/BUILD.bazel b/metropolis/test/e2e/k8s_cts/BUILD.bazel
index 8458b2d..6593a73 100644
--- a/metropolis/test/e2e/k8s_cts/BUILD.bazel
+++ b/metropolis/test/e2e/k8s_cts/BUILD.bazel
@@ -6,7 +6,7 @@
importpath = "source.monogon.dev/metropolis/test/e2e/k8s_cts",
visibility = ["//visibility:private"],
deps = [
- "//metropolis/test/launch/cluster",
+ "//metropolis/test/launch",
"@io_k8s_api//core/v1:core",
"@io_k8s_api//rbac/v1:rbac",
"@io_k8s_apimachinery//pkg/apis/meta/v1:meta",
diff --git a/metropolis/test/e2e/k8s_cts/main.go b/metropolis/test/e2e/k8s_cts/main.go
index 6383cb8..6250a16 100644
--- a/metropolis/test/e2e/k8s_cts/main.go
+++ b/metropolis/test/e2e/k8s_cts/main.go
@@ -34,7 +34,7 @@
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "source.monogon.dev/metropolis/test/launch/cluster"
+ mlaunch "source.monogon.dev/metropolis/test/launch"
)
// makeCTSPodSpec generates a spec for a standalone pod running the Kubernetes
@@ -100,7 +100,7 @@
}()
// TODO(q3k): bump up number of nodes after multi-node workflow gets reimplemented.
- cl, err := cluster.LaunchCluster(ctx, cluster.ClusterOptions{NumNodes: 1})
+ cl, err := mlaunch.LaunchCluster(ctx, mlaunch.ClusterOptions{NumNodes: 1})
if err != nil {
log.Fatalf("Failed to launch cluster: %v", err)
}
diff --git a/metropolis/test/e2e/suites/core/BUILD.bazel b/metropolis/test/e2e/suites/core/BUILD.bazel
index 45ce438..223d7c7 100644
--- a/metropolis/test/e2e/suites/core/BUILD.bazel
+++ b/metropolis/test/e2e/suites/core/BUILD.bazel
@@ -17,12 +17,12 @@
deps = [
"//metropolis/node",
"//metropolis/node/core/rpc",
- "//metropolis/pkg/localregistry",
"//metropolis/proto/api",
"//metropolis/proto/common",
"//metropolis/test/launch",
- "//metropolis/test/launch/cluster",
+ "//metropolis/test/localregistry",
"//metropolis/test/util",
+ "//osbase/test/launch",
"@io_bazel_rules_go//go/runfiles:go_default_library",
"@org_golang_google_grpc//:go_default_library",
],
diff --git a/metropolis/test/e2e/suites/core/run_test.go b/metropolis/test/e2e/suites/core/run_test.go
index 8bbbf52..21640a6 100644
--- a/metropolis/test/e2e/suites/core/run_test.go
+++ b/metropolis/test/e2e/suites/core/run_test.go
@@ -19,10 +19,10 @@
common "source.monogon.dev/metropolis/node"
"source.monogon.dev/metropolis/node/core/rpc"
- "source.monogon.dev/metropolis/pkg/localregistry"
- "source.monogon.dev/metropolis/test/launch"
- "source.monogon.dev/metropolis/test/launch/cluster"
+ mlaunch "source.monogon.dev/metropolis/test/launch"
+ "source.monogon.dev/metropolis/test/localregistry"
"source.monogon.dev/metropolis/test/util"
+ "source.monogon.dev/osbase/test/launch"
apb "source.monogon.dev/metropolis/proto/api"
cpb "source.monogon.dev/metropolis/proto/common"
@@ -62,7 +62,7 @@
t.Fatalf("Creating test image registry failed: %v", err)
}
// Launch cluster.
- clusterOptions := cluster.ClusterOptions{
+ clusterOptions := mlaunch.ClusterOptions{
NumNodes: 2,
LocalRegistry: lr,
InitialClusterConfiguration: &cpb.ClusterConfiguration{
@@ -70,7 +70,7 @@
StorageSecurityPolicy: cpb.ClusterConfiguration_STORAGE_SECURITY_POLICY_NEEDS_INSECURE,
},
}
- cluster, err := cluster.LaunchCluster(ctx, clusterOptions)
+ cluster, err := mlaunch.LaunchCluster(ctx, clusterOptions)
if err != nil {
t.Fatalf("LaunchCluster failed: %v", err)
}
diff --git a/metropolis/test/e2e/suites/ha/BUILD.bazel b/metropolis/test/e2e/suites/ha/BUILD.bazel
index 0a89977..5a2a4dd 100644
--- a/metropolis/test/e2e/suites/ha/BUILD.bazel
+++ b/metropolis/test/e2e/suites/ha/BUILD.bazel
@@ -15,10 +15,10 @@
"resources:ram:7000",
],
deps = [
- "//metropolis/pkg/localregistry",
"//metropolis/test/launch",
- "//metropolis/test/launch/cluster",
+ "//metropolis/test/localregistry",
"//metropolis/test/util",
+ "//osbase/test/launch",
"@io_bazel_rules_go//go/runfiles:go_default_library",
],
)
diff --git a/metropolis/test/e2e/suites/ha/run_test.go b/metropolis/test/e2e/suites/ha/run_test.go
index 63a2acd..cc02df4 100644
--- a/metropolis/test/e2e/suites/ha/run_test.go
+++ b/metropolis/test/e2e/suites/ha/run_test.go
@@ -9,10 +9,10 @@
"github.com/bazelbuild/rules_go/go/runfiles"
- "source.monogon.dev/metropolis/pkg/localregistry"
- "source.monogon.dev/metropolis/test/launch"
- "source.monogon.dev/metropolis/test/launch/cluster"
+ mlaunch "source.monogon.dev/metropolis/test/launch"
+ "source.monogon.dev/metropolis/test/localregistry"
"source.monogon.dev/metropolis/test/util"
+ "source.monogon.dev/osbase/test/launch"
)
const (
@@ -48,12 +48,12 @@
t.Fatalf("Creating test image registry failed: %v", err)
}
// Launch cluster.
- clusterOptions := cluster.ClusterOptions{
+ clusterOptions := mlaunch.ClusterOptions{
NumNodes: 3,
LocalRegistry: lr,
NodeLogsToFiles: true,
}
- cluster, err := cluster.LaunchCluster(ctx, clusterOptions)
+ cluster, err := mlaunch.LaunchCluster(ctx, clusterOptions)
if err != nil {
t.Fatalf("LaunchCluster failed: %v", err)
}
diff --git a/metropolis/test/e2e/suites/ha_cold/BUILD.bazel b/metropolis/test/e2e/suites/ha_cold/BUILD.bazel
index d358f86..c2cfa0d 100644
--- a/metropolis/test/e2e/suites/ha_cold/BUILD.bazel
+++ b/metropolis/test/e2e/suites/ha_cold/BUILD.bazel
@@ -17,7 +17,7 @@
deps = [
"//metropolis/proto/common",
"//metropolis/test/launch",
- "//metropolis/test/launch/cluster",
"//metropolis/test/util",
+ "//osbase/test/launch",
],
)
diff --git a/metropolis/test/e2e/suites/ha_cold/run_test.go b/metropolis/test/e2e/suites/ha_cold/run_test.go
index 6670b8f..419d290 100644
--- a/metropolis/test/e2e/suites/ha_cold/run_test.go
+++ b/metropolis/test/e2e/suites/ha_cold/run_test.go
@@ -6,9 +6,9 @@
"testing"
"time"
- "source.monogon.dev/metropolis/test/launch"
- "source.monogon.dev/metropolis/test/launch/cluster"
+ mlaunch "source.monogon.dev/metropolis/test/launch"
"source.monogon.dev/metropolis/test/util"
+ "source.monogon.dev/osbase/test/launch"
cpb "source.monogon.dev/metropolis/proto/common"
)
@@ -36,7 +36,7 @@
defer cancel()
// Launch cluster.
- clusterOptions := cluster.ClusterOptions{
+ clusterOptions := mlaunch.ClusterOptions{
NumNodes: 3,
NodeLogsToFiles: true,
InitialClusterConfiguration: &cpb.ClusterConfiguration{
@@ -44,7 +44,7 @@
StorageSecurityPolicy: cpb.ClusterConfiguration_STORAGE_SECURITY_POLICY_NEEDS_INSECURE,
},
}
- cluster, err := cluster.LaunchCluster(ctx, clusterOptions)
+ cluster, err := mlaunch.LaunchCluster(ctx, clusterOptions)
if err != nil {
t.Fatalf("LaunchCluster failed: %v", err)
}
diff --git a/metropolis/test/e2e/suites/kubernetes/BUILD.bazel b/metropolis/test/e2e/suites/kubernetes/BUILD.bazel
index 191ba35..6234e94 100644
--- a/metropolis/test/e2e/suites/kubernetes/BUILD.bazel
+++ b/metropolis/test/e2e/suites/kubernetes/BUILD.bazel
@@ -33,8 +33,8 @@
],
deps = [
"//metropolis/node",
- "//metropolis/pkg/localregistry",
- "//metropolis/test/launch/cluster",
+ "//metropolis/test/launch",
+ "//metropolis/test/localregistry",
"//metropolis/test/util",
"@io_bazel_rules_go//go/runfiles:go_default_library",
"@io_k8s_api//core/v1:core",
diff --git a/metropolis/test/e2e/suites/kubernetes/run_test.go b/metropolis/test/e2e/suites/kubernetes/run_test.go
index ec38aa3..f15fafd 100644
--- a/metropolis/test/e2e/suites/kubernetes/run_test.go
+++ b/metropolis/test/e2e/suites/kubernetes/run_test.go
@@ -23,8 +23,8 @@
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
podv1 "k8s.io/kubernetes/pkg/api/v1/pod"
- "source.monogon.dev/metropolis/pkg/localregistry"
- "source.monogon.dev/metropolis/test/launch/cluster"
+ mlaunch "source.monogon.dev/metropolis/test/launch"
+ "source.monogon.dev/metropolis/test/localregistry"
"source.monogon.dev/metropolis/test/util"
common "source.monogon.dev/metropolis/node"
@@ -64,11 +64,11 @@
}
// Launch cluster.
- clusterOptions := cluster.ClusterOptions{
+ clusterOptions := mlaunch.ClusterOptions{
NumNodes: 2,
LocalRegistry: lr,
}
- cluster, err := cluster.LaunchCluster(ctx, clusterOptions)
+ cluster, err := mlaunch.LaunchCluster(ctx, clusterOptions)
if err != nil {
t.Fatalf("LaunchCluster failed: %v", err)
}
diff --git a/metropolis/test/ktest/BUILD.bazel b/metropolis/test/ktest/BUILD.bazel
deleted file mode 100644
index 952d7e8..0000000
--- a/metropolis/test/ktest/BUILD.bazel
+++ /dev/null
@@ -1,59 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
-load("//metropolis/node/build/kconfig-patcher:kconfig-patcher.bzl", "kconfig_patch")
-load("//third_party/linux:def.bzl", "linux_image")
-
-go_library(
- name = "ktest_lib",
- srcs = ["main.go"],
- importpath = "source.monogon.dev/metropolis/test/ktest",
- visibility = ["//visibility:private"],
- deps = ["//metropolis/test/launch"],
-)
-
-go_binary(
- name = "ktest",
- embed = [":ktest_lib"],
- pure = "on",
- visibility = [
- "//go/net/psample:__pkg__",
- "//metropolis:__subpackages__",
- ],
-)
-
-kconfig_patch(
- name = "testing-config",
- src = "//third_party/linux:linux-metropolis.config",
- out = "testing.config",
- override_configs = {
- # Unlock command line
- "CONFIG_CMDLINE_OVERRIDE": "n",
- "CONFIG_CMDLINE_BOOL": "n",
- # Shave off 1 second from boot time
- "CONFIG_SERIO_I8042": "",
- "CONFIG_KEYBOARD_ATKBD": "",
- "CONFIG_RTC_DRV_CMOS": "",
- # Shave off an additional 18ms (half of the boot time)
- "CONFIG_DEBUG_WX": "",
- },
-)
-
-linux_image(
- name = "linux-testing",
- image_format = "vmlinux",
- kernel_config = ":testing-config",
- # This image is directly used by the ktest macro, thus it needs a pretty
- # wide visibility.
- visibility = [
- "//go/net/psample:__pkg__",
- "//metropolis:__subpackages__",
- ],
-)
-
-filegroup(
- name = "test-script",
- srcs = ["run_ktest.sh"],
- visibility = [
- "//go/net/psample:__pkg__",
- "//metropolis:__subpackages__",
- ],
-)
diff --git a/metropolis/test/ktest/init/BUILD.bazel b/metropolis/test/ktest/init/BUILD.bazel
deleted file mode 100644
index e48bcb2..0000000
--- a/metropolis/test/ktest/init/BUILD.bazel
+++ /dev/null
@@ -1,19 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
-
-go_library(
- name = "init_lib",
- srcs = ["main.go"],
- importpath = "source.monogon.dev/metropolis/test/ktest/init",
- visibility = ["//visibility:private"],
- deps = ["@org_golang_x_sys//unix"],
-)
-
-go_binary(
- name = "init",
- embed = [":init_lib"],
- pure = "on",
- visibility = [
- "//go/net/psample:__pkg__",
- "//metropolis:__subpackages__",
- ],
-)
diff --git a/metropolis/test/ktest/init/main.go b/metropolis/test/ktest/init/main.go
deleted file mode 100644
index 16ffa29..0000000
--- a/metropolis/test/ktest/init/main.go
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2020 The Monogon Project Authors.
-//
-// SPDX-License-Identifier: Apache-2.0
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// ktestinit is an init designed to run inside a lightweight VM for running
-// tests in there. It performs basic platform initialization like mounting
-// kernel filesystems and launches the test executable at /tester, passes the
-// exit code back out over the control socket to ktest and then terminates the
-// default VM kernel.
-package main
-
-import (
- "errors"
- "fmt"
- "os"
- "os/exec"
-
- "golang.org/x/sys/unix"
-)
-
-func mountInit() error {
- for _, el := range []struct {
- dir string
- fs string
- flags uintptr
- }{
- {"/sys", "sysfs", unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV},
- {"/sys/kernel/debug", "debugfs", unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV},
- {"/proc", "proc", unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV},
- {"/dev", "devtmpfs", unix.MS_NOEXEC | unix.MS_NOSUID},
- {"/dev/pts", "devpts", unix.MS_NOEXEC | unix.MS_NOSUID},
- {"/tmp", "tmpfs", 0},
- } {
- if err := os.Mkdir(el.dir, 0755); err != nil && !os.IsExist(err) {
- return fmt.Errorf("could not make %s: %w", el.dir, err)
- }
- if err := unix.Mount(el.fs, el.dir, el.fs, el.flags, ""); err != nil {
- return fmt.Errorf("could not mount %s on %s: %w", el.fs, el.dir, err)
- }
- }
- return nil
-}
-
-func main() {
- if err := mountInit(); err != nil {
- panic(err)
- }
-
- // First virtual serial is always stdout, second is control
- ioConn, err := os.OpenFile("/dev/vport1p1", os.O_RDWR, 0)
- if err != nil {
- fmt.Printf("Failed to open communication device: %v\n", err)
- return
- }
- cmd := exec.Command("/tester", "-test.v")
- cmd.Stderr = os.Stderr
- cmd.Stdout = os.Stdout
- cmd.Env = append(cmd.Env, "IN_KTEST=true")
- if err := cmd.Run(); err != nil {
- var exerr *exec.ExitError
- if errors.As(err, &exerr) {
- if _, err := ioConn.Write([]byte{uint8(exerr.ExitCode())}); err != nil {
- panic(err)
- }
- }
- fmt.Printf("Failed to execute tests (tests didn't run): %v", err)
- } else {
- ioConn.Write([]byte{0})
- }
- ioConn.Close()
-
- unix.Reboot(unix.LINUX_REBOOT_CMD_RESTART)
-}
diff --git a/metropolis/test/ktest/ktest.bzl b/metropolis/test/ktest/ktest.bzl
deleted file mode 100644
index 8cb5257..0000000
--- a/metropolis/test/ktest/ktest.bzl
+++ /dev/null
@@ -1,58 +0,0 @@
-# Copyright 2020 The Monogon Project Authors.
-#
-# SPDX-License-Identifier: Apache-2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-Ktest provides a macro to run tests under a normal Metropolis node kernel
-"""
-
-load("//metropolis/node/build:def.bzl", "node_initramfs")
-
-def _dict_union(x, y):
- z = {}
- z.update(x)
- z.update(y)
- return z
-
-def ktest(tester, cmdline = "", files = {}, fsspecs = [], files_cc = {}):
- node_initramfs(
- name = "test_initramfs",
- fsspecs = [
- "//metropolis/node/build:earlydev.fsspec",
- ] + fsspecs,
- files = _dict_union({
- "//metropolis/test/ktest/init": "/init",
- tester: "/tester",
- }, files),
- files_cc = files_cc,
- testonly = True,
- )
-
- native.sh_test(
- name = "ktest",
- args = [
- "$(location //metropolis/test/ktest)",
- "$(location :test_initramfs)",
- "$(location //metropolis/test/ktest:linux-testing)",
- cmdline,
- ],
- size = "small",
- srcs = ["//metropolis/test/ktest:test-script"],
- data = [
- "//metropolis/test/ktest",
- ":test_initramfs",
- "//metropolis/test/ktest:linux-testing",
- ],
- )
diff --git a/metropolis/test/ktest/main.go b/metropolis/test/ktest/main.go
deleted file mode 100644
index 27cd919..0000000
--- a/metropolis/test/ktest/main.go
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2020 The Monogon Project Authors.
-//
-// SPDX-License-Identifier: Apache-2.0
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// ktest is a test launcher for running tests inside a custom kernel and passes
-// the results back out.
-package main
-
-import (
- "context"
- "flag"
- "io"
- "log"
- "os"
- "time"
-
- "source.monogon.dev/metropolis/test/launch"
-)
-
-var (
- kernelPath = flag.String("kernel-path", "", "Path of the Kernel ELF file")
- initrdPath = flag.String("initrd-path", "", "Path of the initrd image")
- cmdline = flag.String("cmdline", "", "Additional kernel command line options")
-)
-
-func main() {
- flag.Parse()
-
- hostFeedbackConn, vmFeedbackConn, err := launch.NewSocketPair()
- if err != nil {
- log.Fatalf("Failed to create socket pair: %v", err)
- }
-
- exitCodeChan := make(chan uint8, 1)
-
- go func() {
- defer hostFeedbackConn.Close()
-
- returnCode := make([]byte, 1)
- if _, err := io.ReadFull(hostFeedbackConn, returnCode); err != nil {
- log.Fatalf("Failed to read socket: %v", err)
- }
- exitCodeChan <- returnCode[0]
- }()
-
- if err := launch.RunMicroVM(context.Background(), &launch.MicroVMOptions{
- Name: "ktest",
- KernelPath: *kernelPath,
- InitramfsPath: *initrdPath,
- Cmdline: *cmdline,
- SerialPort: os.Stdout,
- ExtraChardevs: []*os.File{vmFeedbackConn},
- DisableHostNetworkInterface: true,
- }); err != nil {
- log.Fatalf("Failed to run ktest VM: %v", err)
- }
-
- select {
- case exitCode := <-exitCodeChan:
- os.Exit(int(exitCode))
- case <-time.After(1 * time.Second):
- log.Fatal("Failed to get an error code back (test runtime probably crashed)")
- }
-}
diff --git a/metropolis/test/ktest/run_ktest.sh b/metropolis/test/ktest/run_ktest.sh
deleted file mode 100755
index 02920a1..0000000
--- a/metropolis/test/ktest/run_ktest.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/bash
-exec "$1" -initrd-path "$2" -kernel-path "$3" -cmdline "$4"
\ No newline at end of file
diff --git a/metropolis/test/lacp/BUILD.bazel b/metropolis/test/lacp/BUILD.bazel
index d044de2..b5a0f7a 100644
--- a/metropolis/test/lacp/BUILD.bazel
+++ b/metropolis/test/lacp/BUILD.bazel
@@ -1,5 +1,5 @@
load("@io_bazel_rules_go//go:def.bzl", "go_test")
-load("//metropolis/test/ktest:ktest.bzl", "ktest")
+load("//osbase/test/ktest:ktest.bzl", "ktest")
go_test(
name = "lacptest_test",
diff --git a/metropolis/test/launch/BUILD.bazel b/metropolis/test/launch/BUILD.bazel
index cc5ef6c..24296d1 100644
--- a/metropolis/test/launch/BUILD.bazel
+++ b/metropolis/test/launch/BUILD.bazel
@@ -3,17 +3,50 @@
go_library(
name = "launch",
srcs = [
- "launch.go",
- "log.go",
+ "cluster.go",
+ "insecure_key.go",
+ "metroctl.go",
+ "prefixed_stdio.go",
+ "swtpm.go",
],
data = [
+ "//metropolis/node:image",
+ "//metropolis/test/nanoswitch:initramfs",
+ "//metropolis/test/swtpm/certtool",
+ "//metropolis/test/swtpm/swtpm_cert",
+ "//osbase/test/ktest:linux-testing",
+ "//third_party/edk2:firmware",
"@com_github_bonzini_qboot//:qboot-bin",
+ "@swtpm",
+ "@swtpm//:swtpm_localca",
+ "@swtpm//:swtpm_setup",
],
importpath = "source.monogon.dev/metropolis/test/launch",
- visibility = ["//metropolis:__subpackages__"],
+ visibility = ["//visibility:public"],
deps = [
- "//metropolis/pkg/freeport",
+ "//go/qcow2",
+ "//metropolis/cli/metroctl/core",
+ "//metropolis/node",
+ "//metropolis/node/core/curator/proto/api",
+ "//metropolis/node/core/identity",
+ "//metropolis/node/core/rpc",
+ "//metropolis/node/core/rpc/resolver",
+ "//metropolis/proto/api",
+ "//metropolis/proto/common",
+ "//metropolis/test/localregistry",
+ "//osbase/logbuffer",
+ "//osbase/test/launch",
+ "@com_github_cenkalti_backoff_v4//:backoff",
+ "@com_github_kballard_go_shellquote//:go-shellquote",
"@io_bazel_rules_go//go/runfiles:go_default_library",
+ "@io_k8s_client_go//kubernetes",
+ "@io_k8s_client_go//rest",
+ "@org_golang_google_grpc//:go_default_library",
+ "@org_golang_google_grpc//codes",
+ "@org_golang_google_grpc//status",
+ "@org_golang_google_protobuf//proto",
+ "@org_golang_x_net//proxy",
"@org_golang_x_sys//unix",
+ "@org_uber_go_multierr//:multierr",
],
)
diff --git a/metropolis/test/launch/cli/launch-cluster/BUILD.bazel b/metropolis/test/launch/cli/launch-cluster/BUILD.bazel
index 688228e..9e48795 100644
--- a/metropolis/test/launch/cli/launch-cluster/BUILD.bazel
+++ b/metropolis/test/launch/cli/launch-cluster/BUILD.bazel
@@ -8,7 +8,7 @@
visibility = ["//visibility:private"],
deps = [
"//metropolis/cli/metroctl/core",
- "//metropolis/test/launch/cluster",
+ "//metropolis/test/launch",
],
)
diff --git a/metropolis/test/launch/cli/launch-cluster/main.go b/metropolis/test/launch/cli/launch-cluster/main.go
index c9b9dec..1529396 100644
--- a/metropolis/test/launch/cli/launch-cluster/main.go
+++ b/metropolis/test/launch/cli/launch-cluster/main.go
@@ -23,12 +23,12 @@
"os/signal"
metroctl "source.monogon.dev/metropolis/cli/metroctl/core"
- "source.monogon.dev/metropolis/test/launch/cluster"
+ mlaunch "source.monogon.dev/metropolis/test/launch"
)
func main() {
ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
- cl, err := cluster.LaunchCluster(ctx, cluster.ClusterOptions{
+ cl, err := mlaunch.LaunchCluster(ctx, mlaunch.ClusterOptions{
NumNodes: 3,
NodeLogsToFiles: true,
})
@@ -36,7 +36,7 @@
log.Fatalf("LaunchCluster: %v", err)
}
- mpath, err := cluster.MetroctlRunfilePath()
+ mpath, err := mlaunch.MetroctlRunfilePath()
if err != nil {
log.Fatalf("MetroctlRunfilePath: %v", err)
}
diff --git a/metropolis/test/launch/cli/launch/BUILD.bazel b/metropolis/test/launch/cli/launch/BUILD.bazel
index 49df994..f0edefc 100644
--- a/metropolis/test/launch/cli/launch/BUILD.bazel
+++ b/metropolis/test/launch/cli/launch/BUILD.bazel
@@ -9,7 +9,7 @@
deps = [
"//metropolis/proto/api",
"//metropolis/test/launch",
- "//metropolis/test/launch/cluster",
+ "//osbase/test/launch",
],
)
diff --git a/metropolis/test/launch/cli/launch/main.go b/metropolis/test/launch/cli/launch/main.go
index 2ae3a0c..71c7aa2 100644
--- a/metropolis/test/launch/cli/launch/main.go
+++ b/metropolis/test/launch/cli/launch/main.go
@@ -24,8 +24,9 @@
"path/filepath"
apb "source.monogon.dev/metropolis/proto/api"
- "source.monogon.dev/metropolis/test/launch"
- "source.monogon.dev/metropolis/test/launch/cluster"
+
+ mlaunch "source.monogon.dev/metropolis/test/launch"
+ "source.monogon.dev/osbase/test/launch"
)
func main() {
@@ -45,22 +46,22 @@
defer os.RemoveAll(sd)
var ports []uint16
- for _, p := range cluster.NodePorts {
+ for _, p := range mlaunch.NodePorts {
ports = append(ports, uint16(p))
}
ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
doneC := make(chan error)
- tpmf, err := cluster.NewTPMFactory(filepath.Join(ld, "tpm"))
+ tpmf, err := mlaunch.NewTPMFactory(filepath.Join(ld, "tpm"))
if err != nil {
log.Fatalf("NewTPMFactory: %v", err)
}
- err = cluster.LaunchNode(ctx, ld, sd, tpmf, &cluster.NodeOptions{
+ err = mlaunch.LaunchNode(ctx, ld, sd, tpmf, &mlaunch.NodeOptions{
Name: "test-node",
Ports: launch.IdentityPortMap(ports),
SerialPort: os.Stdout,
NodeParameters: &apb.NodeParameters{
Cluster: &apb.NodeParameters_ClusterBootstrap_{
- ClusterBootstrap: cluster.InsecureClusterBootstrap,
+ ClusterBootstrap: mlaunch.InsecureClusterBootstrap,
},
},
}, doneC)
diff --git a/metropolis/test/launch/cluster/cluster.go b/metropolis/test/launch/cluster.go
similarity index 99%
rename from metropolis/test/launch/cluster/cluster.go
rename to metropolis/test/launch/cluster.go
index dfeb457..7ae5f83 100644
--- a/metropolis/test/launch/cluster/cluster.go
+++ b/metropolis/test/launch/cluster.go
@@ -2,7 +2,7 @@
// nodes and clusters in a virtualized environment using qemu. It's kept in a
// separate package as it depends on a Metropolis node image, which might not be
// required for some use of the launch library.
-package cluster
+package launch
import (
"bytes"
@@ -48,8 +48,8 @@
"source.monogon.dev/metropolis/node/core/identity"
"source.monogon.dev/metropolis/node/core/rpc"
"source.monogon.dev/metropolis/node/core/rpc/resolver"
- "source.monogon.dev/metropolis/pkg/localregistry"
- "source.monogon.dev/metropolis/test/launch"
+ "source.monogon.dev/metropolis/test/localregistry"
+ "source.monogon.dev/osbase/test/launch"
)
const (
@@ -854,7 +854,7 @@
} else {
serialPort = newPrefixedStdio(99)
}
- kernelPath, err := runfiles.Rlocation("_main/metropolis/test/ktest/vmlinux")
+ kernelPath, err := runfiles.Rlocation("_main/osbase/test/ktest/vmlinux")
if err != nil {
launch.Fatal("Failed to resolved nanoswitch kernel: %v", err)
}
diff --git a/metropolis/test/launch/cluster/BUILD.bazel b/metropolis/test/launch/cluster/BUILD.bazel
deleted file mode 100644
index efa04cb..0000000
--- a/metropolis/test/launch/cluster/BUILD.bazel
+++ /dev/null
@@ -1,52 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-go_library(
- name = "cluster",
- srcs = [
- "cluster.go",
- "insecure_key.go",
- "metroctl.go",
- "prefixed_stdio.go",
- "swtpm.go",
- ],
- data = [
- "//metropolis/node:image",
- "//metropolis/test/ktest:linux-testing",
- "//metropolis/test/nanoswitch:initramfs",
- "//metropolis/test/swtpm/certtool",
- "//metropolis/test/swtpm/swtpm_cert",
- "//third_party/edk2:firmware",
- "@com_github_bonzini_qboot//:qboot-bin",
- "@swtpm",
- "@swtpm//:swtpm_localca",
- "@swtpm//:swtpm_setup",
- ],
- importpath = "source.monogon.dev/metropolis/test/launch/cluster",
- visibility = ["//visibility:public"],
- deps = [
- "//go/qcow2",
- "//metropolis/cli/metroctl/core",
- "//metropolis/node",
- "//metropolis/node/core/curator/proto/api",
- "//metropolis/node/core/identity",
- "//metropolis/node/core/rpc",
- "//metropolis/node/core/rpc/resolver",
- "//metropolis/pkg/localregistry",
- "//metropolis/pkg/logbuffer",
- "//metropolis/proto/api",
- "//metropolis/proto/common",
- "//metropolis/test/launch",
- "@com_github_cenkalti_backoff_v4//:backoff",
- "@com_github_kballard_go_shellquote//:go-shellquote",
- "@io_bazel_rules_go//go/runfiles:go_default_library",
- "@io_k8s_client_go//kubernetes",
- "@io_k8s_client_go//rest",
- "@org_golang_google_grpc//:go_default_library",
- "@org_golang_google_grpc//codes",
- "@org_golang_google_grpc//status",
- "@org_golang_google_protobuf//proto",
- "@org_golang_x_net//proxy",
- "@org_golang_x_sys//unix",
- "@org_uber_go_multierr//:multierr",
- ],
-)
diff --git a/metropolis/test/launch/cluster/insecure_key.go b/metropolis/test/launch/insecure_key.go
similarity index 98%
rename from metropolis/test/launch/cluster/insecure_key.go
rename to metropolis/test/launch/insecure_key.go
index 48cd6d8..72af26f 100644
--- a/metropolis/test/launch/cluster/insecure_key.go
+++ b/metropolis/test/launch/insecure_key.go
@@ -14,7 +14,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package cluster
+package launch
import (
"crypto/ed25519"
diff --git a/metropolis/test/launch/launch.go b/metropolis/test/launch/launch.go
deleted file mode 100644
index 953025d..0000000
--- a/metropolis/test/launch/launch.go
+++ /dev/null
@@ -1,335 +0,0 @@
-// Copyright 2020 The Monogon Project Authors.
-//
-// SPDX-License-Identifier: Apache-2.0
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// launch implements test harnesses for running qemu VMs from tests.
-package launch
-
-import (
- "bytes"
- "context"
- "errors"
- "fmt"
- "io"
- "net"
- "os"
- "os/exec"
- "strconv"
- "strings"
- "syscall"
-
- "github.com/bazelbuild/rules_go/go/runfiles"
- "golang.org/x/sys/unix"
-
- "source.monogon.dev/metropolis/pkg/freeport"
-)
-
-type QemuValue map[string][]string
-
-// ToOption encodes structured data into a QEMU option. Example: "test", {"key1":
-// {"val1"}, "key2": {"val2", "val3"}} returns "test,key1=val1,key2=val2,key2=val3"
-func (value QemuValue) ToOption(name string) string {
- var optionValues []string
- if name != "" {
- optionValues = append(optionValues, name)
- }
- for name, values := range value {
- if len(values) == 0 {
- optionValues = append(optionValues, name)
- }
- for _, val := range values {
- optionValues = append(optionValues, fmt.Sprintf("%v=%v", name, val))
- }
- }
- return strings.Join(optionValues, ",")
-}
-
-// PrettyPrintQemuArgs prints the given QEMU arguments to stderr.
-func PrettyPrintQemuArgs(name string, args []string) {
- var argsFmt string
- for _, arg := range args {
- argsFmt += arg
- if !strings.HasPrefix(arg, "-") {
- argsFmt += "\n "
- } else {
- argsFmt += " "
- }
- }
- Log("Running %s:\n %s\n", name, argsFmt)
-}
-
-// PortMap represents where VM ports are mapped to on the host. It maps from the VM
-// port number to the host port number.
-type PortMap map[uint16]uint16
-
-// ToQemuForwards generates QEMU hostfwd values (https://qemu.weilnetz.de/doc/qemu-
-// doc.html#:~:text=hostfwd=) for all mapped ports.
-func (p PortMap) ToQemuForwards() []string {
- var hostfwdOptions []string
- for vmPort, hostPort := range p {
- hostfwdOptions = append(hostfwdOptions, fmt.Sprintf("tcp::%d-:%d", hostPort, vmPort))
- }
- return hostfwdOptions
-}
-
-// IdentityPortMap returns a port map where each given port is mapped onto itself
-// on the host. This is mainly useful for development against Metropolis. The dbg
-// command requires this mapping.
-func IdentityPortMap(ports []uint16) PortMap {
- portMap := make(PortMap)
- for _, port := range ports {
- portMap[port] = port
- }
- return portMap
-}
-
-// ConflictFreePortMap returns a port map where each given port is mapped onto a
-// random free port on the host. This is intended for automated testing where
-// multiple instances of Metropolis nodes might be running. Please call this
-// function for each Launch command separately and as close to it as possible since
-// it cannot guarantee that the ports will remain free.
-func ConflictFreePortMap(ports []uint16) (PortMap, error) {
- portMap := make(PortMap)
- for _, port := range ports {
- mappedPort, listenCloser, err := freeport.AllocateTCPPort()
- if err != nil {
- return portMap, fmt.Errorf("failed to get free host port: %w", err)
- }
- // Defer closing of the listening port until the function is done and all ports are
- // allocated
- defer listenCloser.Close()
- portMap[port] = mappedPort
- }
- return portMap, nil
-}
-
-// GuestServiceMap maps an IP/port combination inside the virtual guest network
-// to a TCPAddr reachable by the host. If the guest connects to the virtual
-// address/port, this connection gets forwarded to the host.
-type GuestServiceMap map[*net.TCPAddr]net.TCPAddr
-
-// ToQemuForwards generates QEMU guestfwd values (https://qemu.weilnetz.de/doc/qemu-
-// doc.html#:~:text=guestfwd=) for all mapped addresses.
-func (p GuestServiceMap) ToQemuForwards() []string {
- var guestfwdOptions []string
- for guestAddr, hostAddr := range p {
- guestfwdOptions = append(guestfwdOptions, fmt.Sprintf("tcp:%s-tcp:%s", guestAddr.String(), hostAddr.String()))
- }
- return guestfwdOptions
-}
-
-// NewSocketPair creates a new socket pair. By connecting both ends to different
-// instances you can connect them with a virtual "network cable". The ends can be
-// passed into the ConnectToSocket option.
-func NewSocketPair() (*os.File, *os.File, error) {
- fds, err := unix.Socketpair(unix.AF_UNIX, syscall.SOCK_STREAM, 0)
- if err != nil {
- return nil, nil, fmt.Errorf("failed to call socketpair: %w", err)
- }
-
- fd1 := os.NewFile(uintptr(fds[0]), "network0")
- fd2 := os.NewFile(uintptr(fds[1]), "network1")
- return fd1, fd2, nil
-}
-
-// HostInterfaceMAC is the MAC address the host SLIRP network interface has if it
-// is not disabled (see DisableHostNetworkInterface in MicroVMOptions)
-var HostInterfaceMAC = net.HardwareAddr{0x02, 0x72, 0x82, 0xbf, 0xc3, 0x56}
-
-// MicroVMOptions contains all options to start a MicroVM
-type MicroVMOptions struct {
- // Name is a human-readable identifier to be used in debug output.
- Name string
-
- // Path to the ELF kernel binary
- KernelPath string
-
- // Path to the Initramfs
- InitramfsPath string
-
- // Cmdline contains additional kernel commandline options
- Cmdline string
-
- // SerialPort is a File(descriptor) over which you can communicate with the serial
- // port of the machine It can be set to an existing file descriptor (like
- // os.Stdout/os.Stderr) or you can use NewSocketPair() to get one end to talk to
- // from Go.
- SerialPort io.Writer
-
- // ExtraChardevs can be used similar to SerialPort, but can contain an arbitrary
- // number of additional serial ports
- ExtraChardevs []*os.File
-
- // ExtraNetworkInterfaces can contain an arbitrary number of file descriptors which
- // are mapped into the VM as virtio network interfaces. The first interface is
- // always a SLIRP-backed interface for communicating with the host.
- ExtraNetworkInterfaces []*os.File
-
- // PortMap contains ports that are mapped to the host through the built-in SLIRP
- // network interface.
- PortMap PortMap
-
- // GuestServiceMap contains TCP services made available in the guest virtual
- // network which are running on the host.
- GuestServiceMap GuestServiceMap
-
- // DisableHostNetworkInterface disables the SLIRP-backed host network interface
- // that is normally the first network interface. If this is set PortMap is ignored.
- // Mostly useful for speeding up QEMU's startup time for tests.
- DisableHostNetworkInterface bool
-
- // PcapDump can be used to dump all network traffic to a pcap file.
- // If unset, no dump is created.
- PcapDump string
-}
-
-// RunMicroVM launches a tiny VM mostly intended for testing. Very quick to boot
-// (<40ms).
-func RunMicroVM(ctx context.Context, opts *MicroVMOptions) error {
- // Generate options for all the file descriptors we'll be passing as virtio "serial
- // ports"
- var extraArgs []string
- for idx := range opts.ExtraChardevs {
- idxStr := strconv.Itoa(idx)
- id := "extra" + idxStr
- // That this works is pretty much a hack, but upstream QEMU doesn't have a
- // bidirectional chardev backend not based around files/sockets on the disk which
- // are a giant pain to work with. We're using QEMU's fdset functionality to make
- // FDs available as pseudo-files and then "ab"using the pipe backend's fallback
- // functionality to get a single bidirectional chardev backend backed by a passed-
- // down RDWR fd. Ref https://lists.gnu.org/archive/html/qemu-devel/2015-
- // 12/msg01256.html
- addFdConf := QemuValue{
- "set": {idxStr},
- "fd": {strconv.Itoa(idx + 3)},
- }
- chardevConf := QemuValue{
- "id": {id},
- "path": {"/dev/fdset/" + idxStr},
- }
- deviceConf := QemuValue{
- "chardev": {id},
- }
- extraArgs = append(extraArgs, "-add-fd", addFdConf.ToOption(""),
- "-chardev", chardevConf.ToOption("pipe"), "-device", deviceConf.ToOption("virtserialport"))
- }
-
- for idx := range opts.ExtraNetworkInterfaces {
- id := fmt.Sprintf("net%v", idx)
- netdevConf := QemuValue{
- "id": {id},
- "fd": {strconv.Itoa(idx + 3 + len(opts.ExtraChardevs))},
- }
- extraArgs = append(extraArgs, "-netdev", netdevConf.ToOption("socket"), "-device", "virtio-net-device,netdev="+id)
- }
-
- // This sets up a minimum viable environment for our Linux kernel. It clears all
- // standard QEMU configuration and sets up a MicroVM machine
- // (https://github.com/qemu/qemu/blob/master/docs/microvm.rst) with all legacy
- // emulation turned off. This means the only "hardware" the Linux kernel inside can
- // communicate with is a single virtio-mmio region. Over that MMIO interface we run
- // a paravirtualized RNG (since the kernel in there has nothing to gather that from
- // and it delays booting), a single paravirtualized console and an arbitrary number
- // of extra serial ports for talking to various things that might run inside. The
- // kernel, initramfs and command line are mapped into VM memory at boot time and
- // not loaded from any sort of disk. Booting and shutting off one of these VMs
- // takes <100ms.
- biosPath, err := runfiles.Rlocation("com_github_bonzini_qboot/bios.bin")
- if err != nil {
- return fmt.Errorf("while searching bios: %w", err)
- }
-
- baseArgs := []string{
- "-nodefaults", "-no-user-config", "-nographic", "-no-reboot",
- "-accel", "kvm", "-cpu", "host",
- "-m", "1G",
- // Needed until QEMU updates their bundled qboot version (needs
- // https://github.com/bonzini/qboot/pull/28)
- "-bios", biosPath,
- "-M", "microvm,x-option-roms=off,pic=off,pit=off,rtc=off,isa-serial=off",
- "-kernel", opts.KernelPath,
- // We force using a triple-fault reboot strategy since otherwise the kernel first
- // tries others (like ACPI) which are not available in this very restricted
- // environment. Similarly we need to override the boot console since there's
- // nothing on the ISA bus that the kernel could talk to. We also force quiet for
- // performance reasons.
- "-append", "reboot=t console=hvc0 quiet " + opts.Cmdline,
- "-initrd", opts.InitramfsPath,
- "-device", "virtio-rng-device,max-bytes=1024,period=1000",
- "-device", "virtio-serial-device,max_ports=16",
- "-chardev", "stdio,id=con0", "-device", "virtconsole,chardev=con0",
- }
-
- if !opts.DisableHostNetworkInterface {
- qemuNetType := "user"
- qemuNetConfig := QemuValue{
- "id": {"usernet0"},
- "net": {"10.42.0.0/24"},
- "dhcpstart": {"10.42.0.10"},
- }
- if opts.PortMap != nil {
- qemuNetConfig["hostfwd"] = opts.PortMap.ToQemuForwards()
- }
- if opts.GuestServiceMap != nil {
- qemuNetConfig["guestfwd"] = opts.GuestServiceMap.ToQemuForwards()
- }
-
- baseArgs = append(baseArgs, "-netdev", qemuNetConfig.ToOption(qemuNetType),
- "-device", "virtio-net-device,netdev=usernet0,mac="+HostInterfaceMAC.String())
- }
-
- if !opts.DisableHostNetworkInterface && opts.PcapDump != "" {
- qemuNetDump := QemuValue{
- "id": {"usernet0"},
- "netdev": {"usernet0"},
- "file": {opts.PcapDump},
- }
- extraArgs = append(extraArgs, "-object", qemuNetDump.ToOption("filter-dump"))
- }
-
- var stdErrBuf bytes.Buffer
- cmd := exec.CommandContext(ctx, "qemu-system-x86_64", append(baseArgs, extraArgs...)...)
- cmd.Stdout = opts.SerialPort
- cmd.Stderr = &stdErrBuf
-
- cmd.ExtraFiles = append(cmd.ExtraFiles, opts.ExtraChardevs...)
- cmd.ExtraFiles = append(cmd.ExtraFiles, opts.ExtraNetworkInterfaces...)
-
- PrettyPrintQemuArgs(opts.Name, cmd.Args)
-
- err = cmd.Run()
- // If it's a context error, just quit. There's no way to tell a
- // killed-due-to-context vs killed-due-to-external-reason error returned by Run,
- // so we approximate by looking at the context's status.
- if err != nil && ctx.Err() != nil {
- return ctx.Err()
- }
-
- var exerr *exec.ExitError
- if err != nil && errors.As(err, &exerr) {
- exerr.Stderr = stdErrBuf.Bytes()
- newErr := QEMUError(*exerr)
- return &newErr
- }
- return err
-}
-
-// QEMUError is a special type of ExitError used when QEMU fails. In addition to
-// normal ExitError features it prints stderr for debugging.
-type QEMUError exec.ExitError
-
-func (e *QEMUError) Error() string {
- return fmt.Sprintf("%v: %v", e.String(), string(e.Stderr))
-}
diff --git a/metropolis/test/launch/log.go b/metropolis/test/launch/log.go
deleted file mode 100644
index 2637e24..0000000
--- a/metropolis/test/launch/log.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package launch
-
-import (
- "fmt"
- "os"
- "strings"
-)
-
-// Log is compatible with the output of ConciseString as used in the Metropolis
-// console log, making the output more readable in unified test logs.
-func Log(f string, args ...any) {
- formatted := fmt.Sprintf(f, args...)
- for i, line := range strings.Split(formatted, "\n") {
- if len(line) == 0 {
- continue
- }
- if i == 0 {
- fmt.Printf("TT| %20s ! %s\n", "test launch", line)
- } else {
- fmt.Printf("TT| %20s | %s\n", "", line)
- }
- }
-}
-
-func Fatal(f string, args ...any) {
- Log(f, args...)
- os.Exit(1)
-}
diff --git a/metropolis/test/launch/cluster/metroctl.go b/metropolis/test/launch/metroctl.go
similarity index 99%
rename from metropolis/test/launch/cluster/metroctl.go
rename to metropolis/test/launch/metroctl.go
index e985a64..e3196a6 100644
--- a/metropolis/test/launch/cluster/metroctl.go
+++ b/metropolis/test/launch/metroctl.go
@@ -1,4 +1,4 @@
-package cluster
+package launch
import (
"context"
diff --git a/metropolis/test/launch/cluster/prefixed_stdio.go b/metropolis/test/launch/prefixed_stdio.go
similarity index 94%
rename from metropolis/test/launch/cluster/prefixed_stdio.go
rename to metropolis/test/launch/prefixed_stdio.go
index 3ea3e18..c851c44 100644
--- a/metropolis/test/launch/cluster/prefixed_stdio.go
+++ b/metropolis/test/launch/prefixed_stdio.go
@@ -1,11 +1,11 @@
-package cluster
+package launch
import (
"fmt"
"io"
"strings"
- "source.monogon.dev/metropolis/pkg/logbuffer"
+ "source.monogon.dev/osbase/logbuffer"
)
// prefixedStdio is a io.ReadWriter which splits written bytes into lines,
diff --git a/metropolis/test/launch/cluster/swtpm.go b/metropolis/test/launch/swtpm.go
similarity index 98%
rename from metropolis/test/launch/cluster/swtpm.go
rename to metropolis/test/launch/swtpm.go
index 0f9b5c5..fa5cb78 100644
--- a/metropolis/test/launch/cluster/swtpm.go
+++ b/metropolis/test/launch/swtpm.go
@@ -1,4 +1,4 @@
-package cluster
+package launch
import (
"context"
@@ -12,7 +12,7 @@
"github.com/bazelbuild/rules_go/go/runfiles"
- "source.monogon.dev/metropolis/test/launch"
+ "source.monogon.dev/osbase/test/launch"
)
// A TPMFactory manufactures virtual TPMs using swtpm.
diff --git a/metropolis/test/localregistry/BUILD.bazel b/metropolis/test/localregistry/BUILD.bazel
new file mode 100644
index 0000000..548960a
--- /dev/null
+++ b/metropolis/test/localregistry/BUILD.bazel
@@ -0,0 +1,19 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+ name = "localregistry",
+ srcs = ["localregistry.go"],
+ importpath = "source.monogon.dev/metropolis/test/localregistry",
+ visibility = ["//visibility:public"],
+ deps = [
+ "//metropolis/test/localregistry/spec",
+ "@com_github_docker_distribution//:distribution",
+ "@com_github_docker_distribution//manifest/manifestlist",
+ "@com_github_docker_distribution//manifest/ocischema",
+ "@com_github_docker_distribution//manifest/schema2",
+ "@com_github_docker_distribution//reference",
+ "@com_github_opencontainers_go_digest//:go-digest",
+ "@io_bazel_rules_go//go/runfiles:go_default_library",
+ "@org_golang_google_protobuf//encoding/prototext",
+ ],
+)
diff --git a/metropolis/test/localregistry/def.bzl b/metropolis/test/localregistry/def.bzl
new file mode 100644
index 0000000..c5fc560
--- /dev/null
+++ b/metropolis/test/localregistry/def.bzl
@@ -0,0 +1,35 @@
+#load("@io_bazel_rules_docker//container:providers.bzl", "ImageInfo")
+
+def _localregistry_manifest_impl(ctx):
+ manifest_out = ctx.actions.declare_file(ctx.label.name+".prototxt")
+
+ images = []
+ referenced = [manifest_out]
+ for i in ctx.attr.images:
+ image_file = i.files.to_list()[0]
+ image = struct(
+ name = i.label.package + "/" + i.label.name,
+ path = image_file.short_path,
+ )
+ referenced.append(image_file)
+ images.append(image)
+
+ ctx.actions.write(manifest_out, proto.encode_text(struct(images = images)))
+ return [DefaultInfo(runfiles = ctx.runfiles(files = referenced), files = depset([manifest_out]))]
+
+
+localregistry_manifest = rule(
+ implementation = _localregistry_manifest_impl,
+ doc = """
+ Builds a manifest for serving images directly from the build files.
+ """,
+ attrs = {
+ "images": attr.label_list(
+ mandatory = True,
+ doc = """
+ List of images to be served from the local registry.
+ """,
+ providers = [],
+ ),
+ },
+)
diff --git a/metropolis/test/localregistry/localregistry.go b/metropolis/test/localregistry/localregistry.go
new file mode 100644
index 0000000..120eb61
--- /dev/null
+++ b/metropolis/test/localregistry/localregistry.go
@@ -0,0 +1,172 @@
+// Package localregistry implements a read-only OCI Distribution / Docker
+// V2 container image registry backed by local layers.
+package localregistry
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strconv"
+
+ "github.com/bazelbuild/rules_go/go/runfiles"
+ "github.com/docker/distribution"
+ "github.com/docker/distribution/manifest/manifestlist"
+ "github.com/docker/distribution/manifest/ocischema"
+ "github.com/docker/distribution/manifest/schema2"
+ "github.com/docker/distribution/reference"
+ "github.com/opencontainers/go-digest"
+ "google.golang.org/protobuf/encoding/prototext"
+
+ "source.monogon.dev/metropolis/test/localregistry/spec"
+)
+
+type Server struct {
+ manifests map[string][]byte
+ blobs map[digest.Digest]blobMeta
+}
+
+type blobMeta struct {
+ filePath string
+ mediaType string
+ contentLength int64
+}
+
+func manifestDescriptorFromBazel(image *spec.Image) (manifestlist.ManifestDescriptor, error) {
+ indexPath, err := runfiles.Rlocation(filepath.Join("_main", image.Path, "index.json"))
+ if err != nil {
+ return manifestlist.ManifestDescriptor{}, fmt.Errorf("while locating manifest list file: %w", err)
+ }
+
+ manifestListRaw, err := os.ReadFile(indexPath)
+ if err != nil {
+ return manifestlist.ManifestDescriptor{}, fmt.Errorf("while opening manifest list file: %w", err)
+ }
+
+ var imageManifestList manifestlist.ManifestList
+ if err := json.Unmarshal(manifestListRaw, &imageManifestList); err != nil {
+ return manifestlist.ManifestDescriptor{}, fmt.Errorf("while unmarshaling manifest list for %q: %w", image.Name, err)
+ }
+
+ if len(imageManifestList.Manifests) != 1 {
+ return manifestlist.ManifestDescriptor{}, fmt.Errorf("unexpected manifest list length > 1")
+ }
+
+ return imageManifestList.Manifests[0], nil
+}
+
+func manifestFromBazel(s *Server, image *spec.Image, md manifestlist.ManifestDescriptor) (ocischema.Manifest, error) {
+ manifestPath, err := runfiles.Rlocation(filepath.Join("_main", image.Path, "blobs", md.Digest.Algorithm().String(), md.Digest.Hex()))
+ if err != nil {
+ return ocischema.Manifest{}, fmt.Errorf("while locating manifest file: %w", err)
+ }
+ manifestRaw, err := os.ReadFile(manifestPath)
+ if err != nil {
+ return ocischema.Manifest{}, fmt.Errorf("while opening manifest file: %w", err)
+ }
+
+ var imageManifest ocischema.Manifest
+ if err := json.Unmarshal(manifestRaw, &imageManifest); err != nil {
+ return ocischema.Manifest{}, fmt.Errorf("while unmarshaling manifest for %q: %w", image.Name, err)
+ }
+
+ // For Digest lookups
+ s.manifests[image.Name] = manifestRaw
+ s.manifests[md.Digest.String()] = manifestRaw
+
+ return imageManifest, nil
+}
+
+func addBazelBlobFromDescriptor(s *Server, image *spec.Image, dd distribution.Descriptor) error {
+ path, err := runfiles.Rlocation(filepath.Join("_main", image.Path, "blobs", dd.Digest.Algorithm().String(), dd.Digest.Hex()))
+ if err != nil {
+ return fmt.Errorf("while locating blob: %w", err)
+ }
+ s.blobs[dd.Digest] = blobMeta{filePath: path, mediaType: dd.MediaType, contentLength: dd.Size}
+ return nil
+}
+
+func FromBazelManifest(mb []byte) (*Server, error) {
+ var bazelManifest spec.Manifest
+ if err := prototext.Unmarshal(mb, &bazelManifest); err != nil {
+ log.Fatalf("failed to parse manifest: %v", err)
+ }
+ s := Server{
+ manifests: make(map[string][]byte),
+ blobs: make(map[digest.Digest]blobMeta),
+ }
+ for _, i := range bazelManifest.Images {
+ md, err := manifestDescriptorFromBazel(i)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := addBazelBlobFromDescriptor(&s, i, md.Descriptor); err != nil {
+ return nil, err
+ }
+
+ m, err := manifestFromBazel(&s, i, md)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := addBazelBlobFromDescriptor(&s, i, m.Config); err != nil {
+ return nil, err
+ }
+ for _, l := range m.Layers {
+ if err := addBazelBlobFromDescriptor(&s, i, l); err != nil {
+ return nil, err
+ }
+ }
+ }
+ return &s, nil
+}
+
+var (
+ versionCheckEp = regexp.MustCompile(`^/v2/$`)
+ manifestEp = regexp.MustCompile("^/v2/(" + reference.NameRegexp.String() + ")/manifests/(" + reference.TagRegexp.String() + "|" + digest.DigestRegexp.String() + ")$")
+ blobEp = regexp.MustCompile("^/v2/(" + reference.NameRegexp.String() + ")/blobs/(" + digest.DigestRegexp.String() + ")$")
+)
+
+func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ if req.Method != http.MethodGet && req.Method != http.MethodHead {
+ w.WriteHeader(http.StatusMethodNotAllowed)
+ fmt.Fprintf(w, "Registry is read-only, only GET and HEAD are allowed\n")
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ if versionCheckEp.MatchString(req.URL.Path) {
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, "{}")
+ return
+ } else if matches := manifestEp.FindStringSubmatch(req.URL.Path); len(matches) > 0 {
+ name := matches[1]
+ manifest, ok := s.manifests[name]
+ if !ok {
+ w.WriteHeader(http.StatusNotFound)
+ fmt.Fprintf(w, "Image not found")
+ return
+ }
+ w.Header().Set("Content-Type", schema2.MediaTypeManifest)
+ w.Header().Set("Content-Length", strconv.FormatInt(int64(len(manifest)), 10))
+ w.WriteHeader(http.StatusOK)
+ io.Copy(w, bytes.NewReader(manifest))
+ } else if matches := blobEp.FindStringSubmatch(req.URL.Path); len(matches) > 0 {
+ bm, ok := s.blobs[digest.Digest(matches[2])]
+ if !ok {
+ w.WriteHeader(http.StatusNotFound)
+ fmt.Fprintf(w, "Blob not found")
+ return
+ }
+ w.Header().Set("Content-Type", bm.mediaType)
+ w.Header().Set("Content-Length", strconv.FormatInt(bm.contentLength, 10))
+ http.ServeFile(w, req, bm.filePath)
+ } else {
+ w.WriteHeader(http.StatusNotFound)
+ }
+}
diff --git a/metropolis/test/localregistry/spec/BUILD.bazel b/metropolis/test/localregistry/spec/BUILD.bazel
new file mode 100644
index 0000000..71253d1
--- /dev/null
+++ b/metropolis/test/localregistry/spec/BUILD.bazel
@@ -0,0 +1,23 @@
+load("@rules_proto//proto:defs.bzl", "proto_library")
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
+
+proto_library(
+ name = "spec_proto",
+ srcs = ["manifest.proto"],
+ visibility = ["//visibility:public"],
+)
+
+go_proto_library(
+ name = "spec_go_proto",
+ importpath = "source.monogon.dev/metropolis/test/localregistry/spec",
+ proto = ":spec_proto",
+ visibility = ["//visibility:public"],
+)
+
+go_library(
+ name = "spec",
+ embed = [":spec_go_proto"],
+ importpath = "source.monogon.dev/metropolis/test/localregistry/spec",
+ visibility = ["//visibility:public"],
+)
diff --git a/metropolis/test/localregistry/spec/gomod-generated-placeholder.go b/metropolis/test/localregistry/spec/gomod-generated-placeholder.go
new file mode 100644
index 0000000..f09cd57
--- /dev/null
+++ b/metropolis/test/localregistry/spec/gomod-generated-placeholder.go
@@ -0,0 +1 @@
+package spec
diff --git a/metropolis/test/localregistry/spec/manifest.proto b/metropolis/test/localregistry/spec/manifest.proto
new file mode 100644
index 0000000..bb53581
--- /dev/null
+++ b/metropolis/test/localregistry/spec/manifest.proto
@@ -0,0 +1,19 @@
+syntax = "proto3";
+
+package monogon.metropolis.pkg.localregistry;
+
+option go_package = "source.monogon.dev/metropolis/test/localregistry/spec";
+
+// Single image metadata
+message Image {
+ // Name of the image (no domain or tag, just slash-separated path)
+ string name = 1;
+ // Path to the image
+ string path = 2;
+}
+
+// Main message
+message Manifest {
+ // List of images for the local registry
+ repeated Image images = 1;
+}
\ No newline at end of file
diff --git a/metropolis/test/nanoswitch/BUILD.bazel b/metropolis/test/nanoswitch/BUILD.bazel
index a3163f5..4954480 100644
--- a/metropolis/test/nanoswitch/BUILD.bazel
+++ b/metropolis/test/nanoswitch/BUILD.bazel
@@ -13,10 +13,10 @@
"//metropolis/node",
"//metropolis/node/core/network/dhcp4c",
"//metropolis/node/core/network/dhcp4c/callback",
- "//metropolis/pkg/logtree",
- "//metropolis/pkg/socksproxy",
- "//metropolis/pkg/supervisor",
- "//metropolis/test/launch",
+ "//osbase/logtree",
+ "//osbase/socksproxy",
+ "//osbase/supervisor",
+ "//osbase/test/launch",
"@com_github_google_nftables//:nftables",
"@com_github_google_nftables//expr",
"@com_github_insomniacslk_dhcp//dhcpv4",
diff --git a/metropolis/test/nanoswitch/nanoswitch.go b/metropolis/test/nanoswitch/nanoswitch.go
index b4ced56..ca1d77d 100644
--- a/metropolis/test/nanoswitch/nanoswitch.go
+++ b/metropolis/test/nanoswitch/nanoswitch.go
@@ -45,9 +45,9 @@
common "source.monogon.dev/metropolis/node"
"source.monogon.dev/metropolis/node/core/network/dhcp4c"
dhcpcb "source.monogon.dev/metropolis/node/core/network/dhcp4c/callback"
- "source.monogon.dev/metropolis/pkg/logtree"
- "source.monogon.dev/metropolis/pkg/supervisor"
- "source.monogon.dev/metropolis/test/launch"
+ "source.monogon.dev/osbase/logtree"
+ "source.monogon.dev/osbase/supervisor"
+ "source.monogon.dev/osbase/test/launch"
)
var switchIP = net.IP{10, 1, 0, 1}
diff --git a/metropolis/test/nanoswitch/socks.go b/metropolis/test/nanoswitch/socks.go
index b8e7107..1230903 100644
--- a/metropolis/test/nanoswitch/socks.go
+++ b/metropolis/test/nanoswitch/socks.go
@@ -5,13 +5,13 @@
"fmt"
"net"
- "source.monogon.dev/metropolis/pkg/socksproxy"
- "source.monogon.dev/metropolis/pkg/supervisor"
+ "source.monogon.dev/osbase/socksproxy"
+ "source.monogon.dev/osbase/supervisor"
)
// SOCKSPort is the port at which nanoswitch listens for SOCKS conenctions.
//
-// ONCHANGE(//metropolis/test/launch/cluster:cluster.go): port must be kept in sync
+// ONCHANGE(//metropolis/test/launch:cluster.go): port must be kept in sync
const SOCKSPort uint16 = 1080
// socksHandler implements a socksproxy.Handler which permits and logs
diff --git a/metropolis/test/swtpm/swtpm_cert/BUILD.bazel b/metropolis/test/swtpm/swtpm_cert/BUILD.bazel
index dce12e6..f0a8050 100644
--- a/metropolis/test/swtpm/swtpm_cert/BUILD.bazel
+++ b/metropolis/test/swtpm/swtpm_cert/BUILD.bazel
@@ -9,7 +9,7 @@
importpath = "source.monogon.dev/metropolis/test/swtpm/swtpm_cert",
visibility = ["//visibility:private"],
deps = [
- "//metropolis/pkg/pki",
+ "//osbase/pki",
"@com_github_spf13_pflag//:pflag",
],
)
diff --git a/metropolis/test/swtpm/swtpm_cert/main.go b/metropolis/test/swtpm/swtpm_cert/main.go
index 7b14f3a..8d5663b 100644
--- a/metropolis/test/swtpm/swtpm_cert/main.go
+++ b/metropolis/test/swtpm/swtpm_cert/main.go
@@ -24,7 +24,7 @@
"github.com/spf13/pflag"
- "source.monogon.dev/metropolis/pkg/pki"
+ "source.monogon.dev/osbase/pki"
)
func getSignkey() *rsa.PrivateKey {
diff --git a/metropolis/test/util/BUILD.bazel b/metropolis/test/util/BUILD.bazel
index 00790a2..3603daf 100644
--- a/metropolis/test/util/BUILD.bazel
+++ b/metropolis/test/util/BUILD.bazel
@@ -12,10 +12,10 @@
deps = [
"//metropolis/node/core/curator/proto/api",
"//metropolis/node/core/identity",
- "//metropolis/pkg/event/memory",
- "//metropolis/pkg/pki",
"//metropolis/proto/common",
- "//metropolis/test/launch",
+ "//osbase/event/memory",
+ "//osbase/pki",
+ "//osbase/test/launch",
"@com_zx2c4_golang_wireguard_wgctrl//wgtypes",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//credentials/insecure",
diff --git a/metropolis/test/util/curator.go b/metropolis/test/util/curator.go
index 39b2610..28fa7b1 100644
--- a/metropolis/test/util/curator.go
+++ b/metropolis/test/util/curator.go
@@ -13,7 +13,7 @@
apb "source.monogon.dev/metropolis/node/core/curator/proto/api"
cpb "source.monogon.dev/metropolis/proto/common"
- "source.monogon.dev/metropolis/pkg/event/memory"
+ "source.monogon.dev/osbase/event/memory"
)
// TestCurator is a shim Curator implementation that serves pending Watch
diff --git a/metropolis/test/util/rpc.go b/metropolis/test/util/rpc.go
index e4caece..030d8d0 100644
--- a/metropolis/test/util/rpc.go
+++ b/metropolis/test/util/rpc.go
@@ -9,7 +9,7 @@
"testing"
"source.monogon.dev/metropolis/node/core/identity"
- "source.monogon.dev/metropolis/pkg/pki"
+ "source.monogon.dev/osbase/pki"
)
// NewEphemeralClusterCredentials creates a set of TLS certificates for use in a
diff --git a/metropolis/test/util/runners.go b/metropolis/test/util/runners.go
index bb7aa91..95de02a 100644
--- a/metropolis/test/util/runners.go
+++ b/metropolis/test/util/runners.go
@@ -9,7 +9,7 @@
"testing"
"time"
- "source.monogon.dev/metropolis/test/launch"
+ "source.monogon.dev/osbase/test/launch"
)
// TestEventual creates a new subtest looping the given function until it either