Merge pull request #20626 from jakecorrenti/ignition-builder

Refactor Ignition configuration for virt providers
This commit is contained in:
openshift-merge-bot[bot]
2023-11-09 19:54:00 +00:00
committed by GitHub
7 changed files with 242 additions and 219 deletions

View File

@ -129,11 +129,9 @@ func (v AppleHVVirtualization) NewMachine(opts machine.InitOptions) (machine.VM,
return nil, err
}
ignitionPath, err := define.NewMachineFile(filepath.Join(configDir, m.Name)+".ign", nil)
if err != nil {
if err := machine.SetIgnitionFile(&m.IgnitionFile, vmtype, m.Name); err != nil {
return nil, err
}
m.IgnitionFile = *ignitionPath
// Set creation time
m.Created = time.Now()

View File

@ -50,6 +50,22 @@ type VfkitHelper struct {
VirtualMachine *vfConfig.VirtualMachine
}
// appleHVReadyUnit is a unit file that sets up the virtual serial device
// where when the VM is done configuring, it will send an ack
// so a listening host knows it can begin interacting with it
const appleHVReadyUnit = `[Unit]
Requires=dev-virtio\\x2dports-%s.device
After=remove-moby.service sshd.socket sshd.service
OnFailure=emergency.target
OnFailureJobMode=isolate
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c '/usr/bin/echo Ready | socat - VSOCK-CONNECT:2:1025'
[Install]
RequiredBy=default.target
`
type MacMachine struct {
// ConfigPath is the fully qualified path to the configuration file
ConfigPath define.VMFile
@ -136,51 +152,6 @@ func (m *MacMachine) addMountsToVM(opts machine.InitOptions, virtiofsMnts *[]mac
return nil
}
// writeIgnitionConfigFile generates the ignition config and writes it to the filesystem
func (m *MacMachine) writeIgnitionConfigFile(opts machine.InitOptions, key string, virtiofsMnts *[]machine.VirtIoFs) error {
// Write the ignition file
ign := machine.DynamicIgnition{
Name: opts.Username,
Key: key,
VMName: m.Name,
VMType: machine.AppleHvVirt,
TimeZone: opts.TimeZone,
WritePath: m.IgnitionFile.GetPath(),
UID: m.UID,
Rootful: m.Rootful,
}
if err := ign.GenerateIgnitionConfig(); err != nil {
return err
}
// ready is a unit file that sets up the virtual serial device
// where when the VM is done configuring, it will send an ack
// so a listening host knows it can being interacting with it
ready := `[Unit]
Requires=dev-virtio\\x2dports-%s.device
After=remove-moby.service sshd.socket sshd.service
OnFailure=emergency.target
OnFailureJobMode=isolate
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c '/usr/bin/echo Ready | socat - VSOCK-CONNECT:2:1025'
[Install]
RequiredBy=default.target
`
readyUnit := machine.Unit{
Enabled: machine.BoolToPtr(true),
Name: "ready.service",
Contents: machine.StrToPtr(fmt.Sprintf(ready, "vsock")),
}
virtiofsUnits := generateSystemDFilesForVirtiofsMounts(*virtiofsMnts)
ign.Cfg.Systemd.Units = append(ign.Cfg.Systemd.Units, readyUnit)
ign.Cfg.Systemd.Units = append(ign.Cfg.Systemd.Units, virtiofsUnits...)
return ign.Write()
}
func (m *MacMachine) Init(opts machine.InitOptions) (bool, error) {
var (
key string
@ -280,6 +251,17 @@ func (m *MacMachine) Init(opts machine.InitOptions) (bool, error) {
return false, err
}
builder := machine.NewIgnitionBuilder(machine.DynamicIgnition{
Name: opts.Username,
Key: key,
VMName: m.Name,
VMType: machine.AppleHvVirt,
TimeZone: opts.TimeZone,
WritePath: m.IgnitionFile.GetPath(),
UID: m.UID,
Rootful: m.Rootful,
})
if len(opts.IgnitionPath) < 1 {
key, err = machine.CreateSSHKeys(m.IdentityPath)
if err != nil {
@ -289,14 +271,22 @@ func (m *MacMachine) Init(opts machine.InitOptions) (bool, error) {
}
if len(opts.IgnitionPath) > 0 {
inputIgnition, err := os.ReadFile(opts.IgnitionPath)
if err != nil {
return false, builder.BuildWithIgnitionFile(opts.IgnitionPath)
}
if err := builder.GenerateIgnitionConfig(); err != nil {
return false, err
}
return false, os.WriteFile(m.IgnitionFile.GetPath(), inputIgnition, 0644)
}
builder.WithUnit(machine.Unit{
Enabled: machine.BoolToPtr(true),
Name: "ready.service",
Contents: machine.StrToPtr(fmt.Sprintf(appleHVReadyUnit, "vsock")),
})
builder.WithUnit(generateSystemDFilesForVirtiofsMounts(virtiofsMnts)...)
// TODO Ignition stuff goes here
err = m.writeIgnitionConfigFile(opts, key, &virtiofsMnts)
err = builder.Build()
callbackFuncs.Add(m.IgnitionFile.Delete)
return err == nil, err

View File

@ -126,11 +126,9 @@ func (v HyperVVirtualization) NewMachine(opts machine.InitOptions) (machine.VM,
m.ConfigPath = *configPath
ignitionPath, err := define.NewMachineFile(filepath.Join(configDir, m.Name)+".ign", nil)
if err != nil {
if err := machine.SetIgnitionFile(&m.IgnitionFile, vmtype, m.Name); err != nil {
return nil, err
}
m.IgnitionFile = *ignitionPath
// Set creation time
m.Created = time.Now()

View File

@ -43,6 +43,58 @@ const (
apiUpTimeout = 20 * time.Second
)
// hyperVReadyUnit is a unit file that sets up the virtual serial device
// where when the VM is done configuring, it will send an ack
// so a listening host knows it can begin interacting with it
//
// VSOCK-CONNECT:2 <- shortcut to connect to the hostvm
const hyperVReadyUnit = `[Unit]
After=remove-moby.service sshd.socket sshd.service
After=systemd-user-sessions.service
OnFailure=emergency.target
OnFailureJobMode=isolate
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c '/usr/bin/echo Ready | socat - VSOCK-CONNECT:2:%d'
[Install]
RequiredBy=default.target
`
// hyperVVsockNetUnit is a systemd unit file that calls the vm helper utility
// needed to take traffic from a network vsock0 device to the actual vsock
// and onto the host
const hyperVVsockNetUnit = `
[Unit]
Description=vsock_network
After=NetworkManager.service
[Service]
ExecStart=/usr/libexec/podman/gvforwarder -preexisting -iface vsock0 -url vsock://2:%d/connect
ExecStartPost=/usr/bin/nmcli c up vsock0
[Install]
WantedBy=multi-user.target
`
const hyperVVsockNMConnection = `
[connection]
id=vsock0
type=tun
interface-name=vsock0
[tun]
mode=2
[802-3-ethernet]
cloned-mac-address=5A:94:EF:E4:0C:EE
[ipv4]
method=auto
[proxy]
`
type HyperVMachine struct {
// ConfigPath is the fully qualified path to the configuration file
ConfigPath define.VMFile
@ -93,104 +145,6 @@ func (m *HyperVMachine) addNetworkAndReadySocketsToRegistry() error {
return nil
}
// writeIgnitionConfigFile generates the ignition config and writes it to the
// filesystem
func (m *HyperVMachine) writeIgnitionConfigFile(opts machine.InitOptions, user, key string) error {
ign := machine.DynamicIgnition{
Name: user,
Key: key,
VMName: m.Name,
VMType: machine.HyperVVirt,
TimeZone: opts.TimeZone,
WritePath: m.IgnitionFile.GetPath(),
UID: m.UID,
Rootful: m.Rootful,
}
if err := ign.GenerateIgnitionConfig(); err != nil {
return err
}
// ready is a unit file that sets up the virtual serial device
// where when the VM is done configuring, it will send an ack
// so a listening host knows it can being interacting with it
//
// VSOCK-CONNECT:2 <- shortcut to connect to the hostvm
ready := `[Unit]
After=remove-moby.service sshd.socket sshd.service
After=systemd-user-sessions.service
OnFailure=emergency.target
OnFailureJobMode=isolate
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c '/usr/bin/echo Ready | socat - VSOCK-CONNECT:2:%d'
[Install]
RequiredBy=default.target
`
readyUnit := machine.Unit{
Enabled: machine.BoolToPtr(true),
Name: "ready.service",
Contents: machine.StrToPtr(fmt.Sprintf(ready, m.ReadyHVSock.Port)),
}
// userNetwork is a systemd unit file that calls the vm helpoer utility
// needed to take traffic from a network vsock0 device to the actual vsock
// and onto the host
userNetwork := `
[Unit]
Description=vsock_network
After=NetworkManager.service
[Service]
ExecStart=/usr/libexec/podman/gvforwarder -preexisting -iface vsock0 -url vsock://2:%d/connect
ExecStartPost=/usr/bin/nmcli c up vsock0
[Install]
WantedBy=multi-user.target
`
vsockNetUnit := machine.Unit{
Contents: machine.StrToPtr(fmt.Sprintf(userNetwork, m.NetworkHVSock.Port)),
Enabled: machine.BoolToPtr(true),
Name: "vsock-network.service",
}
ign.Cfg.Systemd.Units = append(ign.Cfg.Systemd.Units, readyUnit, vsockNetUnit)
vSockNMConnection := `
[connection]
id=vsock0
type=tun
interface-name=vsock0
[tun]
mode=2
[802-3-ethernet]
cloned-mac-address=5A:94:EF:E4:0C:EE
[ipv4]
method=auto
[proxy]
`
ign.Cfg.Storage.Files = append(ign.Cfg.Storage.Files, machine.File{
Node: machine.Node{
Path: "/etc/NetworkManager/system-connections/vsock0.nmconnection",
},
FileEmbedded1: machine.FileEmbedded1{
Append: nil,
Contents: machine.Resource{
Source: machine.EncodeDataURLPtr(vSockNMConnection),
},
Mode: machine.IntToPtr(0600),
},
})
return ign.Write()
}
// readAndSplitIgnition reads the ignition file and splits it into key:value pairs
func (m *HyperVMachine) readAndSplitIgnition() error {
ignFile, err := m.IgnitionFile.Read()
@ -295,14 +249,21 @@ func (m *HyperVMachine) Init(opts machine.InitOptions) (bool, error) {
}
m.Rootful = opts.Rootful
builder := machine.NewIgnitionBuilder(machine.DynamicIgnition{
Name: m.RemoteUsername,
Key: key,
VMName: m.Name,
VMType: machine.HyperVVirt,
TimeZone: opts.TimeZone,
WritePath: m.IgnitionFile.GetPath(),
UID: m.UID,
Rootful: m.Rootful,
})
// If the user provides an ignition file, we need to
// copy it into the conf dir
if len(opts.IgnitionPath) > 0 {
inputIgnition, err := os.ReadFile(opts.IgnitionPath)
if err != nil {
return false, err
}
return false, os.WriteFile(m.IgnitionFile.GetPath(), inputIgnition, 0644)
return false, builder.BuildWithIgnitionFile(opts.IgnitionPath)
}
callbackFuncs.Add(m.IgnitionFile.Delete)
@ -310,8 +271,36 @@ func (m *HyperVMachine) Init(opts machine.InitOptions) (bool, error) {
return false, err
}
// Write the ignition file
if err := m.writeIgnitionConfigFile(opts, m.RemoteUsername, key); err != nil {
if err := builder.GenerateIgnitionConfig(); err != nil {
return false, err
}
builder.WithUnit(machine.Unit{
Enabled: machine.BoolToPtr(true),
Name: "ready.service",
Contents: machine.StrToPtr(fmt.Sprintf(hyperVReadyUnit, m.ReadyHVSock.Port)),
})
builder.WithUnit(machine.Unit{
Contents: machine.StrToPtr(fmt.Sprintf(hyperVVsockNetUnit, m.NetworkHVSock.Port)),
Enabled: machine.BoolToPtr(true),
Name: "vsock-network.service",
})
builder.WithFile(machine.File{
Node: machine.Node{
Path: "/etc/NetworkManager/system-connections/vsock0.nmconnection",
},
FileEmbedded1: machine.FileEmbedded1{
Append: nil,
Contents: machine.Resource{
Source: machine.EncodeDataURLPtr(hyperVVsockNMConnection),
},
Mode: machine.IntToPtr(0600),
},
})
if err := builder.Build(); err != nil {
return false, err
}

View File

@ -14,6 +14,7 @@ import (
"github.com/containers/common/libnetwork/etchosts"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v4/pkg/machine/define"
"github.com/sirupsen/logrus"
)
@ -694,3 +695,65 @@ func GetPodmanDockerTmpConfig(uid int, rootful bool, newline bool) string {
return fmt.Sprintf("L+ /run/docker.sock - - - - %s%s", podmanSock, suffix)
}
// SetIgnitionFile creates a new Machine File for the machine's ignition file
// and assignes the handle to `loc`
func SetIgnitionFile(loc *define.VMFile, vmtype VMType, vmName string) error {
vmConfigDir, err := GetConfDir(vmtype)
if err != nil {
return err
}
ignitionFile, err := define.NewMachineFile(filepath.Join(vmConfigDir, vmName+".ign"), nil)
if err != nil {
return err
}
*loc = *ignitionFile
return nil
}
type IgnitionBuilder struct {
dynamicIgnition DynamicIgnition
units []Unit
}
// NewIgnitionBuilder generates a new IgnitionBuilder type using the
// base `DynamicIgnition` object
func NewIgnitionBuilder(dynamicIgnition DynamicIgnition) IgnitionBuilder {
return IgnitionBuilder{
dynamicIgnition,
[]Unit{},
}
}
// GenerateIgnitionConfig generates the ignition config
func (i *IgnitionBuilder) GenerateIgnitionConfig() error {
return i.dynamicIgnition.GenerateIgnitionConfig()
}
// WithUnit adds systemd units to the internal `DynamicIgnition` config
func (i *IgnitionBuilder) WithUnit(units ...Unit) {
i.dynamicIgnition.Cfg.Systemd.Units = append(i.dynamicIgnition.Cfg.Systemd.Units, units...)
}
// WithFile adds storage files to the internal `DynamicIgnition` config
func (i *IgnitionBuilder) WithFile(files ...File) {
i.dynamicIgnition.Cfg.Storage.Files = append(i.dynamicIgnition.Cfg.Storage.Files, files...)
}
// BuildWithIgnitionFile copies the provided ignition file into the internal
// `DynamicIgnition` write path
func (i *IgnitionBuilder) BuildWithIgnitionFile(ignPath string) error {
inputIgnition, err := os.ReadFile(ignPath)
if err != nil {
return err
}
return os.WriteFile(i.dynamicIgnition.WritePath, inputIgnition, 0644)
}
// Build writes the internal `DynamicIgnition` config to its write path
func (i *IgnitionBuilder) Build() error {
return i.dynamicIgnition.Write()
}

View File

@ -64,10 +64,6 @@ func (v *MachineVM) setNewMachineCMD(qemuBinary string, cmdOpts *setNewMachineCM
// NewMachine initializes an instance of a virtual machine based on the qemu
// virtualization.
func (p *QEMUVirtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) {
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return nil, err
}
vm := new(MachineVM)
if len(opts.Name) > 0 {
vm.Name = opts.Name
@ -79,11 +75,9 @@ func (p *QEMUVirtualization) NewMachine(opts machine.InitOptions) (machine.VM, e
}
// set VM ignition file
ignitionFile, err := define.NewMachineFile(filepath.Join(vmConfigDir, vm.Name+".ign"), nil)
if err != nil {
if err := machine.SetIgnitionFile(&vm.IgnitionFile, vmtype, vm.Name); err != nil {
return nil, err
}
vm.IgnitionFile = *ignitionFile
// set VM image file
imagePath, err := define.NewMachineFile(opts.ImagePath, nil)

View File

@ -45,6 +45,23 @@ const (
dockerConnectTimeout = 5 * time.Second
)
// qemuReadyUnit is a unit file that sets up the virtual serial device
// where when the VM is done configuring, it will send an ack
// so a listening host tknows it can begin interacting with it
const qemuReadyUnit = `[Unit]
Requires=dev-virtio\\x2dports-%s.device
After=remove-moby.service sshd.socket sshd.service
After=systemd-user-sessions.service
OnFailure=emergency.target
OnFailureJobMode=isolate
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c '/usr/bin/echo Ready >/dev/%s'
[Install]
RequiredBy=default.target
`
type MachineVM struct {
// ConfigPath is the path to the configuration file
ConfigPath define.VMFile
@ -202,50 +219,6 @@ func (v *MachineVM) addMountsToVM(opts machine.InitOptions) error {
return nil
}
// writeIgnitionConfigFile generates the ignition config and writes it to the
// filesystem
func (v *MachineVM) writeIgnitionConfigFile(opts machine.InitOptions, key string) error {
ign := &machine.DynamicIgnition{
Name: opts.Username,
Key: key,
VMName: v.Name,
VMType: machine.QemuVirt,
TimeZone: opts.TimeZone,
WritePath: v.getIgnitionFile(),
UID: v.UID,
Rootful: v.Rootful,
}
if err := ign.GenerateIgnitionConfig(); err != nil {
return err
}
// ready is a unit file that sets up the virtual serial device
// where when the VM is done configuring, it will send an ack
// so a listening host knows it can being interacting with it
ready := `[Unit]
Requires=dev-virtio\\x2dports-%s.device
After=remove-moby.service sshd.socket sshd.service
After=systemd-user-sessions.service
OnFailure=emergency.target
OnFailureJobMode=isolate
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c '/usr/bin/echo Ready >/dev/%s'
[Install]
RequiredBy=default.target
`
readyUnit := machine.Unit{
Enabled: machine.BoolToPtr(true),
Name: "ready.service",
Contents: machine.StrToPtr(fmt.Sprintf(ready, "vport1p1", "vport1p1")),
}
ign.Cfg.Systemd.Units = append(ign.Cfg.Systemd.Units, readyUnit)
return ign.Write()
}
// Init writes the json configuration file to the filesystem for
// other verbs (start, stop)
func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
@ -327,17 +300,35 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
logrus.Warn("ignoring init option to disable user-mode networking: this mode is not supported by the QEMU backend")
}
builder := machine.NewIgnitionBuilder(machine.DynamicIgnition{
Name: opts.Username,
Key: key,
VMName: v.Name,
VMType: machine.QemuVirt,
TimeZone: opts.TimeZone,
WritePath: v.getIgnitionFile(),
UID: v.UID,
Rootful: v.Rootful,
})
// If the user provides an ignition file, we need to
// copy it into the conf dir
if len(opts.IgnitionPath) > 0 {
inputIgnition, err := os.ReadFile(opts.IgnitionPath)
if err != nil {
return false, err
}
return false, os.WriteFile(v.getIgnitionFile(), inputIgnition, 0644)
return false, builder.BuildWithIgnitionFile(opts.IgnitionPath)
}
err = v.writeIgnitionConfigFile(opts, key)
if err := builder.GenerateIgnitionConfig(); err != nil {
return false, err
}
readyUnit := machine.Unit{
Enabled: machine.BoolToPtr(true),
Name: "ready.service",
Contents: machine.StrToPtr(fmt.Sprintf(qemuReadyUnit, "vport1p1", "vport1p1")),
}
builder.WithUnit(readyUnit)
err = builder.Build()
callbackFuncs.Add(v.IgnitionFile.Delete)
return err == nil, err