Andrey Smirnov 9379cf9ee1 refactor: expose provision as public package
This change is only moving packages and updating import paths.

Goal: expose `internal/pkg/provision` as `pkg/provision` to enable other
projects to import Talos provisioning library.

As cluster checks are almost always required as part of provisioning
process, package `internal/pkg/cluster` was also made public as
`pkg/cluster`.

Other changes were direct dependencies discovered by `importvet` which
were updated.

Public packages (useful, general purpose packages with stable API):

* `internal/pkg/conditions` -> `pkg/conditions`
* `internal/pkg/tail` -> `pkg/tail`

Private packages (used only on provisioning library internally):

* `internal/pkg/inmemhttp` -> `pkg/provision/internal/inmemhttp`
* `internal/pkg/kernel/vmlinuz` -> `pkg/provision/internal/vmlinuz`
* `internal/pkg/cniutils` -> `pkg/provision/internal/cniutils`

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
2020-08-12 05:12:05 -07:00

159 lines
4.6 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package firecracker
import (
"context"
"fmt"
"net"
"os"
"strings"
"time"
firecracker "github.com/firecracker-microvm/firecracker-go-sdk"
"github.com/talos-systems/talos/pkg/provision/providers/vm"
)
// LaunchConfig is passed in to the Launch function over stdin.
type LaunchConfig struct {
GatewayAddr net.IP
Config string
BootloaderEmulation bool
FirecrackerConfig firecracker.Config
}
// Launch a control process around firecracker VM manager.
//
// This function is invoked from 'talosctl firecracker-launch' hidden command
// and wraps starting, controlling and restarting 'firecracker' VM process.
//
// Launch restarts VM forever until control process is stopped itself with a signal.
//
// Process is expected to receive configuration on stdin. Current working directory
// should be cluster state directory, process output should be redirected to the
// logfile in state directory.
//
// When signals SIGINT, SIGTERM are received, control process stops firecracker and exits.
//
//nolint: gocyclo
func Launch() error {
var config LaunchConfig
if err := vm.ReadConfig(&config); err != nil {
return err
}
c := vm.ConfigureSignals()
ctx := context.Background()
httpServer, err := vm.NewConfigServer(config.GatewayAddr, []byte(config.Config))
if err != nil {
return err
}
// patch kernel args
config.FirecrackerConfig.KernelArgs = strings.ReplaceAll(config.FirecrackerConfig.KernelArgs, "{TALOS_CONFIG_URL}", fmt.Sprintf("http://%s/config.yaml", httpServer.GetAddr()))
// save original kernel/initrd asset paths, so that we can re-use them if assets can't be extracted with NewBootLoader
origKernelImagePath, origInitrdPath := config.FirecrackerConfig.KernelImagePath, config.FirecrackerConfig.InitrdPath
httpServer.Serve()
defer httpServer.Shutdown(ctx) //nolint: errcheck
for {
err := func() error {
var (
err error
bootLoader *BootLoader
)
// reset kernel/initrd assets to default values
// bootloader (if enabled) might overwrite them with extracted assets
config.FirecrackerConfig.KernelImagePath, config.FirecrackerConfig.InitrdPath = origKernelImagePath, origInitrdPath
bootLoader, err = NewBootLoader(*config.FirecrackerConfig.Drives[0].PathOnHost)
if err != nil {
// print err but continue boot process
fmt.Fprintf(os.Stderr, "error initializing bootloader: %s\n", err.Error())
} else {
defer bootLoader.Close() //nolint: errcheck
var assets BootAssets
assets, err = bootLoader.ExtractAssets()
if err != nil {
fmt.Fprintf(os.Stderr, "error extracting kernel assets: %s\n", err.Error())
} else if config.BootloaderEmulation {
// boot partition found, pass kernel & initrd from boot partition to Firecracker
config.FirecrackerConfig.KernelImagePath = assets.KernelPath
config.FirecrackerConfig.InitrdPath = assets.InitrdPath
fmt.Fprintf(os.Stderr, "successfully extracted boot assets from the disk image\n")
}
}
cmd := firecracker.VMCommandBuilder{}.
WithBin("firecracker").
WithSocketPath(config.FirecrackerConfig.SocketPath).
WithStdin(os.Stdin).
WithStdout(os.Stdout).
WithStderr(os.Stderr).
Build(ctx)
// reset static configuration, as it gets set each time CNI runs
config.FirecrackerConfig.NetworkInterfaces[0].StaticConfiguration = nil
m, err := firecracker.NewMachine(ctx, config.FirecrackerConfig, firecracker.WithProcessRunner(cmd))
if err != nil {
return fmt.Errorf("failed to create new machine: %w", err)
}
if err := m.Start(ctx); err != nil {
return fmt.Errorf("failed to initialize machine: %w", err)
}
waitCh := make(chan error)
go func() {
waitCh <- m.Wait(ctx)
}()
select {
case err := <-waitCh:
if err != nil {
return fmt.Errorf("failed running VM: %w", err)
}
select {
case sig := <-c:
fmt.Fprintf(os.Stderr, "exiting VM as signal %s was received\n", sig)
return fmt.Errorf("process stopped")
case <-time.After(500 * time.Millisecond): // wait a bit to prevent crash loop
}
case sig := <-c:
fmt.Fprintf(os.Stderr, "stopping VM as signal %s was received\n", sig)
m.StopVMM() //nolint: errcheck
<-waitCh // wait for process to exit
return fmt.Errorf("process stopped")
}
// restart the vm by proceeding with the for loop
os.Remove(config.FirecrackerConfig.SocketPath) //nolint: errcheck
return nil
}()
if err != nil {
return err
}
}
}