blob: a2e00bc90295002b9b807a0cc6472c56b54d62b1 [file] [log] [blame]
// 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.
package main
import (
"context"
"fmt"
"io"
"log"
"os"
"os/signal"
"syscall"
"time"
grpcretry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
"google.golang.org/grpc"
common "source.monogon.dev/metropolis/node"
"source.monogon.dev/metropolis/pkg/logbuffer"
apb "source.monogon.dev/metropolis/proto/api"
"source.monogon.dev/metropolis/test/launch"
)
// prefixedStdout is a os.Stdout proxy that prefixes every line with a constant
// prefix. This is used to show logs from two Metropolis nodes without getting
// them confused.
// TODO(q3k): move to logging API instead of relying on qemu stdout, and remove
// this function.
func prefixedStdout(prefix string) io.ReadWriter {
lb := logbuffer.NewLineBuffer(2048, func(l *logbuffer.Line) {
fmt.Fprintf(os.Stdout, "%s%s\n", prefix, l.Data)
})
// Make a ReaderWriter from LineBuffer (a Reader), by combining into an
// anonymous struct with a io.MultiReader() (which will always return EOF
// on every Read if given no underlying readers).
return struct {
io.Reader
io.Writer
}{
Reader: io.MultiReader(),
Writer: lb,
}
}
func main() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
ctx, cancel := context.WithCancel(context.Background())
go func() {
<-sigs
cancel()
}()
sw0, vm0, err := launch.NewSocketPair()
if err != nil {
log.Fatalf("Failed to create network pipe: %v\n", err)
}
sw1, vm1, err := launch.NewSocketPair()
if err != nil {
log.Fatalf("Failed to create network pipe: %v\n", err)
}
go func() {
if err := launch.Launch(ctx, launch.Options{
ConnectToSocket: vm0,
SerialPort: prefixedStdout("1| "),
}); err != nil {
log.Fatalf("Failed to launch vm0: %v", err)
}
}()
nanoswitchPortMap := make(launch.PortMap)
identityPorts := []uint16{
common.ExternalServicePort,
common.DebugServicePort,
common.KubernetesAPIPort,
}
for _, port := range identityPorts {
nanoswitchPortMap[port] = port
}
go func() {
opts := []grpcretry.CallOption{
grpcretry.WithBackoff(grpcretry.BackoffExponential(100 * time.Millisecond)),
}
conn, err := nanoswitchPortMap.DialGRPC(common.DebugServicePort, grpc.WithInsecure(),
grpc.WithUnaryInterceptor(grpcretry.UnaryClientInterceptor(opts...)))
if err != nil {
panic(err)
}
defer conn.Close()
debug := apb.NewNodeDebugServiceClient(conn)
res, err := debug.GetGoldenTicket(ctx, &apb.GetGoldenTicketRequest{
// HACK: this is assigned by DHCP, and we assume that everything goes well.
ExternalIp: "10.1.0.3",
}, grpcretry.WithMax(10))
if err != nil {
log.Fatalf("Failed to get golden ticket: %v", err)
}
ec := &apb.EnrolmentConfig{
GoldenTicket: res.Ticket,
}
if err := launch.Launch(ctx, launch.Options{
ConnectToSocket: vm1,
EnrolmentConfig: ec,
SerialPort: prefixedStdout("2| "),
}); err != nil {
log.Fatalf("Failed to launch vm1: %v", err)
}
}()
if err := launch.RunMicroVM(ctx, &launch.MicroVMOptions{
SerialPort: os.Stdout,
KernelPath: "metropolis/test/ktest/linux-testing.elf",
InitramfsPath: "metropolis/test/nanoswitch/initramfs.lz4",
ExtraNetworkInterfaces: []*os.File{sw0, sw1},
PortMap: nanoswitchPortMap,
}); err != nil {
log.Fatalf("Failed to launch nanoswitch: %v", err)
}
}