+
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 24 additions & 23 deletions cmd/vfkit/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,7 @@ func runVFKit(vmConfig *config.VirtualMachine, opts *cmdline.Options) error {
func runVirtualMachine(vmConfig *config.VirtualMachine, vm *vf.VirtualMachine) error {
if vm.Config().Ignition != nil {
go func() {
file, err := os.Open(vmConfig.Ignition.ConfigPath)
if err != nil {
log.Error(err)
}
defer file.Close()
reader := file
if err := startIgnitionProvisionerServer(reader, vmConfig.Ignition.SocketPath); err != nil {
if err := startIgnitionProvisionerServer(vm, vmConfig.Ignition.ConfigPath, vmConfig.Ignition.VsockPort); err != nil {
log.Error(err)
}
log.Debug("ignition vsock server exited")
Expand All @@ -224,7 +218,7 @@ func runVirtualMachine(vmConfig *config.VirtualMachine, vm *vf.VirtualMachine) e
port := vsock.Port
socketURL := vsock.SocketURL
if socketURL == "" {
// the timesync code adds a vsock device without an associated URL.
// timesync and ignition add a vsock device without an associated URL.
continue
}
var listenStr string
Expand Down Expand Up @@ -277,38 +271,45 @@ func runVirtualMachine(vmConfig *config.VirtualMachine, vm *vf.VirtualMachine) e
return <-errCh
}

func startIgnitionProvisionerServer(ignitionReader io.Reader, ignitionSocketPath string) error {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
_, err := io.Copy(w, ignitionReader)
if err != nil {
log.Errorf("failed to serve ignition file: %v", err)
}
})

listener, err := net.Listen("unix", ignitionSocketPath)
func startIgnitionProvisionerServer(vm *vf.VirtualMachine, configPath string, vsockPort uint32) error {
ignitionReader, err := os.Open(configPath)
if err != nil {
return err
}
defer ignitionReader.Close()

util.RegisterExitHandler(func() {
os.Remove(ignitionSocketPath)
})
vsockDevices := vm.SocketDevices()
if len(vsockDevices) != 1 {
return fmt.Errorf("VM has too many/not enough virtio-vsock devices (%d)", len(vsockDevices))
}
listener, err := vsockDevices[0].Listen(vsockPort)
if err != nil {
return err
}

defer func() {
if err := listener.Close(); err != nil {
log.Error(err)
}
}()

return startIgnitionProvisionerServerInternal(ignitionReader, listener)
}

func startIgnitionProvisionerServerInternal(ignitionReader io.ReadSeeker, listener net.Listener) error {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
http.ServeContent(w, req, "", time.Time{}, ignitionReader)
})

srv := &http.Server{
Handler: mux,
Addr: ignitionSocketPath,
Addr: listener.Addr().String(),
ReadHeaderTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
}

log.Debugf("ignition socket: %s", ignitionSocketPath)
log.Debugf("ignition socket: %s", listener.Addr().String())
return srv.Serve(listener)
}

Expand Down
7 changes: 5 additions & 2 deletions cmd/vfkit/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ func TestStartIgnitionProvisionerServer(t *testing.T) {
ignitionData := []byte("ignition configuration")
ignitionReader := bytes.NewReader(ignitionData)

listener, err := net.Listen("unix", socketPath)
require.NoError(t, err)
defer listener.Close()

// Start the server using the socket so that it can returns the ignition data
go func() {
err := startIgnitionProvisionerServer(ignitionReader, socketPath)
require.NoError(t, err)
_ = startIgnitionProvisionerServerInternal(ignitionReader, listener)
}()

// Wait for the socket file to be created before serving, up to 2 seconds
Expand Down
31 changes: 13 additions & 18 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
"math"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"

Expand All @@ -43,7 +42,8 @@ type TimeSync struct {

type Ignition struct {
ConfigPath string `json:"configPath"`
SocketPath string `json:"socketPath"`
SocketPath string `json:"socketPath,omitempty"`
VsockPort uint32 `json:"-"`
}

// The VMComponent interface represents a VM element (device, bootloader, ...)
Expand All @@ -54,8 +54,9 @@ type VMComponent interface {
}

const (
ignitionVsockPort uint = 1024
ignitionSocketName string = "ignition.sock"
// the ignition vsock port is hardcoded to 1024 in ignition source code:
// https://github.com/coreos/ignition/blob/d4ff84b2c28a28ad828b974befe3575563faacdd/internal/providers/applehv/applehv.go#L59-L68
ignitionVsockPort uint32 = 1024
)

// NewVirtualMachine creates a new VirtualMachine instance. The virtual machine
Expand Down Expand Up @@ -222,13 +223,13 @@ func (vm *VirtualMachine) TimeSync() *TimeSync {
return vm.Timesync
}

func IgnitionNew(configPath string, socketPath string) (*Ignition, error) {
if configPath == "" || socketPath == "" {
return nil, fmt.Errorf("config path and socket path cannot be empty")
func IgnitionNew(configPath string, _ string) (*Ignition, error) {
if configPath == "" {
return nil, fmt.Errorf("config path cannot be empty")
}
return &Ignition{
ConfigPath: configPath,
SocketPath: socketPath,
VsockPort: ignitionVsockPort,
}, nil
}

Expand All @@ -238,16 +239,10 @@ func (vm *VirtualMachine) AddIgnitionFileFromCmdLine(cmdlineOpts string) error {
}
opts := strings.Split(cmdlineOpts, ",")
if len(opts) != 1 {
return fmt.Errorf("ignition only accept one option in command line argument")
return fmt.Errorf("ignition only accepts one option in command line argument")
}

socketPath := filepath.Join(os.TempDir(), ignitionSocketName)
dev, err := VirtioVsockNew(ignitionVsockPort, socketPath, true)
if err != nil {
return err
}
vm.Devices = append(vm.Devices, dev)
ignition, err := IgnitionNew(opts[0], socketPath)
ignition, err := IgnitionNew(opts[0], "")
if err != nil {
return err
}
Expand All @@ -261,7 +256,7 @@ func TimeSyncNew(vsockPort uint) (VMComponent, error) {
return nil, fmt.Errorf("invalid vsock port: %d", vsockPort)
}
return &TimeSync{
VsockPort: uint32(vsockPort), //#nosec G115 -- was compared to math.MaxUint32
VsockPort: uint32(vsockPort),
}, nil
}

Expand All @@ -281,7 +276,7 @@ func (ts *TimeSync) FromOptions(options []option) error {
if err != nil {
return err
}
ts.VsockPort = uint32(vsockPort) //#nosec G115 -- ParseUint(_, _, 32) guarantees no overflow
ts.VsockPort = uint32(vsockPort)
default:
return fmt.Errorf("unknown option for timesync parameter: %s", option.key)
}
Expand Down
5 changes: 2 additions & 3 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,15 @@ import (
func TestAddIgnitionFile_MultipleOptions(t *testing.T) {
vm := &VirtualMachine{}
err := vm.AddIgnitionFileFromCmdLine("file1,file2")
assert.EqualError(t, err, "ignition only accept one option in command line argument")
assert.EqualError(t, err, "ignition only accepts one option in command line argument")
}

func TestAddIgnitionFile_OneOption(t *testing.T) {
vm := &VirtualMachine{}
err := vm.AddIgnitionFileFromCmdLine("file1")
require.NoError(t, err)
assert.Len(t, vm.Devices, 1)
assert.Equal(t, uint32(ignitionVsockPort), vm.Devices[0].(*VirtioVsock).Port)
assert.Equal(t, "file1", vm.Ignition.ConfigPath)
assert.Equal(t, ignitionVsockPort, vm.Ignition.VsockPort)
}

func TestNetworkBlockDevice(t *testing.T) {
Expand Down
3 changes: 2 additions & 1 deletion pkg/config/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,10 @@ var jsonTests = map[string]jsonTest{
ignition, err := IgnitionNew("config", "socket")
require.NoError(t, err)
vm.Ignition = ignition
ignition.VsockPort = 0 // slight hack for the test to pass as VsockPort is not serialized
return vm
},
expectedJSON: `{"vcpus":3,"memoryBytes":4194304000,"bootloader":{"kind":"linuxBootloader","vmlinuzPath":"/vmlinuz","initrdPath":"/initrd","kernelCmdLine":"console=hvc0"}, "ignition":{"kind":"ignition","configPath":"config","socketPath":"socket"}}`,
expectedJSON: `{"vcpus":3,"memoryBytes":4194304000,"bootloader":{"kind":"linuxBootloader","vmlinuzPath":"/vmlinuz","initrdPath":"/initrd","kernelCmdLine":"console=hvc0"}, "ignition":{"kind":"ignition","configPath":"config"}}`,
},
"TestVirtioRNG": {
newVM: func(t *testing.T) *VirtualMachine {
Expand Down
4 changes: 2 additions & 2 deletions pkg/config/virtio.go
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ func VirtioVsockNew(port uint, socketURL string, listen bool) (VirtioDevice, err
return nil, fmt.Errorf("invalid vsock port: %d", port)
}
return &VirtioVsock{
Port: uint32(port), //#nosec G115 -- was compared to math.MaxUint32
Port: uint32(port),
SocketURL: socketURL,
Listen: listen,
}, nil
Expand Down Expand Up @@ -652,7 +652,7 @@ func (dev *VirtioVsock) FromOptions(options []option) error {
if err != nil {
return err
}
dev.Port = uint32(port) //#nosec G115 -- ParseUint(_, _, 32) guarantees no overflow
dev.Port = uint32(port)
case "listen":
dev.Listen = true
case "connect":
Expand Down
26 changes: 16 additions & 10 deletions pkg/vf/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,13 @@ func NewVirtualMachine(vmConfig config.VirtualMachine) (*VirtualMachine, error)
vfConfig.SetPlatformVirtualMachineConfiguration(platformConfig)
}

return &VirtualMachine{
vm := &VirtualMachine{
vfConfig: vfConfig,
}, nil
}

func (vm *VirtualMachine) Start() error {
if vm.VirtualMachine == nil {
if err := vm.toVz(); err != nil {
return err
}
}
return vm.VirtualMachine.Start()
if err := vm.toVz(); err != nil {
return nil, err
}
return vm, nil
}

func (vm *VirtualMachine) toVz() error {
Expand Down Expand Up @@ -153,6 +148,17 @@ func (cfg *VirtualMachineConfiguration) toVz() (*vz.VirtualMachineConfiguration,
}
}

if cfg.config.Ignition != nil && cfg.config.Ignition.VsockPort != 0 {
// automatically add the vsock device we'll need for communication over VsockPort
vsockDev := VirtioVsock{
Port: cfg.config.Ignition.VsockPort,
Listen: false,
}
if err := vsockDev.AddToVirtualMachineConfig(cfg); err != nil {
return nil, err
}
}

cfg.SetStorageDevicesVirtualMachineConfiguration(cfg.storageDevicesConfiguration)
cfg.SetDirectorySharingDevicesVirtualMachineConfiguration(cfg.directorySharingDevicesConfiguration)
cfg.SetPointingDevicesVirtualMachineConfiguration(cfg.pointingDevicesConfiguration)
Expand Down
2 changes: 1 addition & 1 deletion pkg/vf/vsock.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func listenVsock(vm *VirtualMachine, port uint32, vsockPath string) (io.Closer,
if len(socketDevices) != 1 {
return nil, fmt.Errorf("VM has too many/not enough virtio-vsock devices (%d)", len(socketDevices))
}
return socketDevices[0].Listen(uint32(port)) //#nosec G115 -- strconv.ParseUint(_, _, 32) guarantees no overflow
return socketDevices[0].Listen(uint32(port))
default:
return nil, fmt.Errorf("unexpected scheme '%s'", parsed.Scheme)
}
Expand Down
5 changes: 3 additions & 2 deletions test/osprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ func kernelArch() string {
}
}

const puipuiVersion = "1.0.3"

func downloadPuipui(destDir string) ([]string, error) {
const puipuiVersion = "0.0.1"
var puipuiURL = fmt.Sprintf("https://github.com/Code-Hex/puipui-linux/releases/download/v%s/puipui_linux_v%s_%s.tar.gz", puipuiVersion, puipuiVersion, kernelArch())

// https://github.com/cavaliergopher/grab/issues/104
Expand Down Expand Up @@ -179,7 +180,7 @@ func findKernel(files []string) (string, error) {
}

func (puipui *PuiPuiProvider) Fetch(destDir string) error {
log.Infof("downloading puipui to %s", destDir)
log.Infof("downloading puipui v%s to %s", puipuiVersion, destDir)
files, err := downloadPuipui(destDir)
if err != nil {
return err
Expand Down
6 changes: 6 additions & 0 deletions test/vm_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ func (vm *testVM) AddDevice(t *testing.T, dev config.VirtioDevice) {
require.NoError(t, err)
}

func (vm *testVM) AddIgnition(t *testing.T, ignConfigPath string) {
ign, err := config.IgnitionNew(ignConfigPath, "")
require.NoError(t, err)
vm.config.Ignition = ign
}

func (vm *testVM) Start(t *testing.T) {
vm.vfkitCmd = startVfkit(t, vm.config)
vm.restSocketPath = vm.vfkitCmd.restSocketPath
Expand Down
30 changes: 30 additions & 0 deletions test/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,3 +444,33 @@ func TestCloudInit(t *testing.T) {
log.Info("stopping vm")
vm.Stop(t)
}

// host listens over vsock, guest connects to the host
func TestIgnition(t *testing.T) {
const ignTestData = "ignition config test\n"
puipuiProvider := NewPuipuiProvider()
log.Info("fetching os image")
err := puipuiProvider.Fetch(t.TempDir())
require.NoError(t, err)

vm := NewTestVM(t, puipuiProvider)
defer vm.Close(t)
require.NotNil(t, vm)

ignConfigPath := filepath.Join(t.TempDir(), "config.ign")
err = os.WriteFile(ignConfigPath, []byte(ignTestData), 0600)
require.NoError(t, err)
vm.AddIgnition(t, ignConfigPath)

vm.AddSSH(t, "tcp")

vm.Start(t)
vm.WaitForSSH(t)

output, err := vm.SSHCombinedOutput(t, "socat -T 2 TCP-LISTEN:8080 VSOCK-CONNECT:2:1024 >/dev/null & curl -q http://localhost:8080")
require.NoError(t, err)
require.Contains(t, string(output), ignTestData)

// time.Sleep(3600 * time.Second)
vm.Stop(t)
}
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载