Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add s390x support to libvirt provider #1249

Closed
Closed
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
294 changes: 261 additions & 33 deletions pkg/adaptor/cloud/libvirt/libvirt.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ import (
libvirtxml "libvirt.org/go/libvirtxml"
)

// architecture value for the s390x architecture
const archS390x = "s390x"

type domainConfig struct {
name string
cpu uint
mem uint
networkName string
bootDisk string
cidataDisk string
}

func createCloudInitISO(v *vmConfig, libvirtClient *libvirtClient) string {
logger.Printf("Create cloudInit iso\n")
cloudInitIso := libvirtClient.dataDir + "/" + v.name + "-cloudinit.iso"
Expand Down Expand Up @@ -49,7 +61,7 @@ func createCloudInitISO(v *vmConfig, libvirtClient *libvirtClient) string {
}
udf.Close()

fmt.Printf("Executing genisoimage\n")
logger.Println("Executing genisoimage")
// genisoimage -output cloudInitIso.iso -volid cidata -joliet -rock user-data meta-data
cmd := exec.Command("genisoimage", "-output", cloudInitIso, "-volid", "cidata", "-joliet", "-rock", userDataFile, v.metaData)
cmd.Stdout = os.Stdout
Expand Down Expand Up @@ -95,7 +107,7 @@ func checkDomainExistsById(id uint32, libvirtClient *libvirtClient) (exist bool,

func uploadIso(isoFile string, isoVolName string, libvirtClient *libvirtClient) (string, error) {

fmt.Printf("Uploading iso file: %s\n", isoFile)
logger.Printf("Uploading iso file: %s\n", isoFile)
volumeDef := newDefVolume(isoVolName)

img, err := newImage(isoFile)
Expand All @@ -116,55 +128,201 @@ func uploadIso(isoFile string, isoVolName string, libvirtClient *libvirtClient)

}

func CreateDomain(ctx context.Context, libvirtClient *libvirtClient, v *vmConfig) (result *createDomainOutput, err error) {
func getGuestForArchType(caps *libvirtxml.Caps, arch string, virttype string) (*libvirtxml.CapsGuest, error) {
for _, guest := range caps.Guests {
if guest.Arch.Name == arch && guest.OSType == virttype {
return &guest, nil
}
}
return nil, fmt.Errorf("could not find any guests for architecture type %s/%s", virttype, arch)
}

v.cpu = uint(2)
v.mem = uint(8)
v.rootDiskSize = uint64(10)
func getHostCapabilities(client *libvirtClient) (*libvirtxml.Caps, error) {
// We should perhaps think of storing this on the connect object
// on first call to avoid the back and forth
capsXML, err := client.connection.GetCapabilities()
if err != nil {
return nil, fmt.Errorf("unable to get capabilities, cause: %w", err)
}

exists, err := checkDomainExistsByName(v.name, libvirtClient)
caps := &libvirtxml.Caps{}
err = xml.Unmarshal([]byte(capsXML), caps)
if err != nil {
return nil, fmt.Errorf("Error in checking instance: %s", err)
return nil, fmt.Errorf("unable to unmarshal capabilities, cause: %w", err)
}
if exists {
logger.Printf("Instance already exists ")
return &createDomainOutput{
instance: v,
}, nil

return caps, nil
}

func lookupMachine(machines []libvirtxml.CapsGuestMachine, targetmachine string) string {
for _, machine := range machines {
if machine.Name == targetmachine {
if machine.Canonical != "" {
return machine.Canonical
}
return machine.Name
}
}
return ""
}

rootVolName := v.name + "-root.qcow2"
err = createVolume(rootVolName, v.rootDiskSize, libvirtClient.volName, libvirtClient)
func getCanonicalMachineName(caps *libvirtxml.Caps, arch string, virttype string, targetmachine string) (string, error) {
guest, err := getGuestForArchType(caps, arch, virttype)
if err != nil {
return nil, fmt.Errorf("Error in creating volume: %s", err)
return "", err
}

cloudInitIso := createCloudInitISO(v, libvirtClient)
name := lookupMachine(guest.Arch.Machines, targetmachine)
if name != "" {
return name, nil
}

isoVolName := v.name + "-cloudinit.iso"
isoVolFile, err := uploadIso(cloudInitIso, isoVolName, libvirtClient)
for _, domain := range guest.Arch.Domains {
name := lookupMachine(domain.Machines, targetmachine)
if name != "" {
return name, nil
}
}

return "", fmt.Errorf("cannot find machine type %s for %s/%s in %v", targetmachine, virttype, arch, caps)
}

func createDomainXMLs390x(client *libvirtClient, cfg *domainConfig) (*libvirtxml.Domain, error) {

caps, err := getHostCapabilities(client)
if err != nil {
return nil, fmt.Errorf("Error in uploading iso volume: %s", err)
return nil, err
}

rootVol, err := getVolume(libvirtClient, rootVolName)
guest, err := getGuestForArchType(caps, archS390x, "hvm")
if err != nil {
return nil, fmt.Errorf("Error retrieving volume: %s", err)
return nil, err
}

rootVolFile, err := rootVol.GetPath()
canonicalmachine, err := getCanonicalMachineName(caps, archS390x, "hvm", "s390-ccw-virtio")
if err != nil {
return nil, fmt.Errorf("Error retrieving volume path: %s", err)
return nil, err
}

// Gen Domain XML.
bootDisk := libvirtxml.DomainDisk{
Device: "disk",
Target: &libvirtxml.DomainDiskTarget{
Dev: "vda",
Bus: "virtio",
},
Driver: &libvirtxml.DomainDiskDriver{
Name: "qemu",
Type: "qcow2",
IOMMU: "on",
},
Source: &libvirtxml.DomainDiskSource{
File: &libvirtxml.DomainDiskSourceFile{
File: cfg.bootDisk,
},
},
Boot: &libvirtxml.DomainDeviceBoot{
Order: 1,
},
}

cloudInitDisk := libvirtxml.DomainDisk{
Device: "disk",
Target: &libvirtxml.DomainDiskTarget{
Dev: "vdb",
Bus: "virtio",
},
Driver: &libvirtxml.DomainDiskDriver{
Name: "qemu",
Type: "raw",
IOMMU: "on",
},
Source: &libvirtxml.DomainDiskSource{
File: &libvirtxml.DomainDiskSourceFile{
File: cfg.cidataDisk,
},
},
}

return &libvirtxml.Domain{
Type: "kvm",
Name: cfg.name,
Description: "This Virtual Machine is the peer-pod VM",
OS: &libvirtxml.DomainOS{
Type: &libvirtxml.DomainOSType{
Type: "hvm",
Arch: archS390x,
Machine: canonicalmachine,
},
},
Metadata: &libvirtxml.DomainMetadata{},
Memory: &libvirtxml.DomainMemory{
Value: cfg.mem, Unit: "GiB",
},
CurrentMemory: &libvirtxml.DomainCurrentMemory{
Value: cfg.mem, Unit: "GiB",
},
VCPU: &libvirtxml.DomainVCPU{
Value: cfg.cpu,
},
Clock: &libvirtxml.DomainClock{
Offset: "utc",
},
Devices: &libvirtxml.DomainDeviceList{
Disks: []libvirtxml.DomainDisk{
bootDisk,
cloudInitDisk,
},
Emulator: guest.Arch.Emulator,
MemBalloon: &libvirtxml.DomainMemBalloon{
Model: "none",
},
RNGs: []libvirtxml.DomainRNG{
{
Model: "virtio",
Backend: &libvirtxml.DomainRNGBackend{
Random: &libvirtxml.DomainRNGBackendRandom{Device: "/dev/urandom"},
},
},
},
Consoles: []libvirtxml.DomainConsole{
{
Source: &libvirtxml.DomainChardevSource{
Pty: &libvirtxml.DomainChardevSourcePty{},
},
Target: &libvirtxml.DomainConsoleTarget{
Type: "sclp",
},
},
},
Interfaces: []libvirtxml.DomainInterface{
{
Model: &libvirtxml.DomainInterfaceModel{
Type: "virtio",
},
Source: &libvirtxml.DomainInterfaceSource{
Network: &libvirtxml.DomainInterfaceSourceNetwork{
Network: cfg.networkName,
},
},
Driver: &libvirtxml.DomainInterfaceDriver{
IOMMU: "on",
},
},
},
},
}, nil

}

func createDomainXMLx86_64(client *libvirtClient, cfg *domainConfig) (*libvirtxml.Domain, error) {

var diskControllerAddr uint = 0
domCfg := &libvirtxml.Domain{
return &libvirtxml.Domain{
Type: "kvm",
Name: v.name,
Name: cfg.name,
Description: "This Virtual Machine is the peer-pod VM",
Memory: &libvirtxml.DomainMemory{Value: uint(v.mem), Unit: "GiB", DumpCore: "on"},
VCPU: &libvirtxml.DomainVCPU{Value: uint(v.cpu)},
Memory: &libvirtxml.DomainMemory{Value: uint(cfg.mem), Unit: "GiB", DumpCore: "on"},
VCPU: &libvirtxml.DomainVCPU{Value: uint(cfg.cpu)},
OS: &libvirtxml.DomainOS{
Type: &libvirtxml.DomainOSType{Arch: "x86_64", Type: "hvm"},
},
Expand All @@ -184,7 +342,7 @@ func CreateDomain(ctx context.Context, libvirtClient *libvirtClient, v *vmConfig
Driver: &libvirtxml.DomainDiskDriver{Type: "qcow2"},
Source: &libvirtxml.DomainDiskSource{
File: &libvirtxml.DomainDiskSourceFile{
File: rootVolFile}},
File: cfg.bootDisk}},
Target: &libvirtxml.DomainDiskTarget{
Dev: "sda", Bus: "sata"},
Boot: &libvirtxml.DomainDeviceBoot{Order: 1},
Expand All @@ -196,7 +354,7 @@ func CreateDomain(ctx context.Context, libvirtClient *libvirtClient, v *vmConfig
Device: "cdrom",
Driver: &libvirtxml.DomainDiskDriver{Name: "qemu", Type: "raw"},
Source: &libvirtxml.DomainDiskSource{
File: &libvirtxml.DomainDiskSourceFile{File: isoVolFile},
File: &libvirtxml.DomainDiskSourceFile{File: cfg.cidataDisk},
},
Target: &libvirtxml.DomainDiskTarget{Dev: "hda", Bus: "ide"},
ReadOnly: &libvirtxml.DomainDiskReadOnly{},
Expand All @@ -208,7 +366,7 @@ func CreateDomain(ctx context.Context, libvirtClient *libvirtClient, v *vmConfig
// Network Interfaces.
Interfaces: []libvirtxml.DomainInterface{
{
Source: &libvirtxml.DomainInterfaceSource{Network: &libvirtxml.DomainInterfaceSourceNetwork{Network: libvirtClient.networkName}},
Source: &libvirtxml.DomainInterfaceSource{Network: &libvirtxml.DomainInterfaceSourceNetwork{Network: cfg.networkName}},
Model: &libvirtxml.DomainInterfaceModel{Type: "virtio"},
},
},
Expand All @@ -219,6 +377,76 @@ func CreateDomain(ctx context.Context, libvirtClient *libvirtClient, v *vmConfig
},
},
},
}, nil
}

// createDomainXML detects the machine type of the libvirt host and will return a libvirt XML for that machine type
func createDomainXML(client *libvirtClient, cfg *domainConfig) (*libvirtxml.Domain, error) {
node, err := client.connection.GetNodeInfo()
if err != nil {
return nil, fmt.Errorf("error retrieving node info: %w", err)
}
switch node.Model {
case archS390x:
return createDomainXMLs390x(client, cfg)
default:
return createDomainXMLx86_64(client, cfg)
}
}

func CreateDomain(ctx context.Context, libvirtClient *libvirtClient, v *vmConfig) (result *createDomainOutput, err error) {

v.cpu = uint(2)
v.mem = uint(8)
v.rootDiskSize = uint64(10)

exists, err := checkDomainExistsByName(v.name, libvirtClient)
if err != nil {
return nil, fmt.Errorf("Error in checking instance: %s", err)
}
if exists {
logger.Printf("Instance already exists ")
return &createDomainOutput{
instance: v,
}, nil
}

rootVolName := v.name + "-root.qcow2"
err = createVolume(rootVolName, v.rootDiskSize, libvirtClient.volName, libvirtClient)
if err != nil {
return nil, fmt.Errorf("Error in creating volume: %s", err)
}

cloudInitIso := createCloudInitISO(v, libvirtClient)

isoVolName := v.name + "-cloudinit.iso"
isoVolFile, err := uploadIso(cloudInitIso, isoVolName, libvirtClient)
if err != nil {
return nil, fmt.Errorf("Error in uploading iso volume: %s", err)
}

rootVol, err := getVolume(libvirtClient, rootVolName)
if err != nil {
return nil, fmt.Errorf("Error retrieving volume: %s", err)
}

rootVolFile, err := rootVol.GetPath()
if err != nil {
return nil, fmt.Errorf("Error retrieving volume path: %s", err)
}

domainCfg := domainConfig{
name: v.name,
cpu: v.cpu,
mem: v.mem,
networkName: libvirtClient.networkName,
bootDisk: rootVolFile,
cidataDisk: isoVolFile,
}

domCfg, err := createDomainXML(libvirtClient, &domainCfg)
if err != nil {
return nil, fmt.Errorf("error building the libvirt XML, cause: %w", err)
}

logger.Printf("Create XML for '%s'", v.name)
Expand Down Expand Up @@ -370,7 +598,7 @@ func NewLibvirtClient(libvirtCfg Config) (*libvirtClient, error) {
return nil, fmt.Errorf("can't find storage pool %q: %v", libvirtCfg.PoolName, err)
}

fmt.Printf("Created libvirt connection")
logger.Println("Created libvirt connection")

return &libvirtClient{
connection: conn,
Expand Down
Loading