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/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.