github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/container/kvm/libvirt/domainxml.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package libvirt
     5  
     6  import (
     7  	"encoding/xml"
     8  	"fmt"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/utils/arch"
    12  )
    13  
    14  // Details of the domain XML format are at: https://libvirt.org/formatdomain.html
    15  // We only use a subset, just enough to create instances in the pool. We don't
    16  // check any argument types here. We expect incoming params to be validate-able
    17  // by a function on the incoming domainParams.
    18  
    19  // DiskInfo represents the type and location of a libvirt pool image.
    20  type DiskInfo interface {
    21  	// Source is the path to the disk image.
    22  	Source() string
    23  	// Driver is the type of disk, qcow, vkmd, raw, etc...
    24  	Driver() string
    25  }
    26  
    27  // InterfaceInfo represents network interface parameters for a kvm domain.
    28  type InterfaceInfo interface {
    29  	// MAC returns the interfaces MAC address.
    30  	MACAddress() string
    31  	// ParentInterfaceName returns the interface's parent device name.
    32  	ParentInterfaceName() string
    33  	// InterfaceName returns the interface's device name.
    34  	InterfaceName() string
    35  }
    36  
    37  type domainParams interface {
    38  	// Arch returns the arch for which we want to generate the domainXML.
    39  	Arch() string
    40  	// CPUs returns the number of CPUs to use.
    41  	CPUs() uint64
    42  	// DiskInfo returns the disk information for the domain.
    43  	DiskInfo() []DiskInfo
    44  	// Host returns the host name.
    45  	Host() string
    46  	// Loader returns the path to the EFI firmware blob to UEFI boot into an
    47  	// image. This is a read-only "pflash" drive.
    48  	Loader() string
    49  	// NetworkInfo contains the network interfaces to create in the domain.
    50  	NetworkInfo() []InterfaceInfo
    51  	// RAM returns the amount of RAM to use.
    52  	RAM() uint64
    53  	// ValidateDomainParams returns nil if the domainParams are valid.
    54  	ValidateDomainParams() error
    55  }
    56  
    57  // NewDomain returns a guest domain suitable for unmarshaling (as XML) onto the
    58  // target host.
    59  func NewDomain(p domainParams) (Domain, error) {
    60  	if err := p.ValidateDomainParams(); err != nil {
    61  		return Domain{}, errors.Trace(err)
    62  	}
    63  
    64  	d := Domain{
    65  		Type:          "kvm",
    66  		Name:          p.Host(),
    67  		VCPU:          p.CPUs(),
    68  		CurrentMemory: Memory{Unit: "MiB", Text: p.RAM()},
    69  		Memory:        Memory{Unit: "MiB", Text: p.RAM()},
    70  		OS:            generateOSElement(p),
    71  		Features:      generateFeaturesElement(p),
    72  		CPU:           generateCPU(p),
    73  		Disk:          []Disk{},
    74  		Interface:     []Interface{},
    75  		Serial: Serial{
    76  			Type: "pty",
    77  			Source: SerialSource{
    78  				Path: "/dev/pts/2",
    79  			},
    80  			Target: SerialTarget{
    81  				Port: 0,
    82  			},
    83  		},
    84  		Console: []Console{
    85  			{
    86  				Type: "pty",
    87  				TTY:  "/dev/pts/2",
    88  				Target: ConsoleTarget{
    89  					Port: 0,
    90  				},
    91  				Source: ConsoleSource{
    92  					Path: "/dev/pts/2",
    93  				},
    94  			},
    95  		},
    96  	}
    97  	for i, diskInfo := range p.DiskInfo() {
    98  		devID, err := deviceID(i)
    99  		if err != nil {
   100  			return Domain{}, errors.Trace(err)
   101  		}
   102  		switch diskInfo.Driver() {
   103  		case "raw":
   104  			d.Disk = append(d.Disk, Disk{
   105  				Device: "disk",
   106  				Type:   "file",
   107  				Driver: DiskDriver{Type: diskInfo.Driver(), Name: "qemu"},
   108  				Source: DiskSource{File: diskInfo.Source()},
   109  				Target: DiskTarget{Dev: devID},
   110  			})
   111  		case "qcow2":
   112  			d.Disk = append(d.Disk, Disk{
   113  				Device: "disk",
   114  				Type:   "file",
   115  				Driver: DiskDriver{Type: diskInfo.Driver(), Name: "qemu"},
   116  				Source: DiskSource{File: diskInfo.Source()},
   117  				Target: DiskTarget{Dev: devID},
   118  			})
   119  		default:
   120  			return Domain{}, errors.Errorf(
   121  				"unsupported disk type %q", diskInfo.Driver())
   122  		}
   123  	}
   124  	for _, iface := range p.NetworkInfo() {
   125  		d.Interface = append(d.Interface, Interface{
   126  			Type:   "bridge",
   127  			MAC:    InterfaceMAC{Address: iface.MACAddress()},
   128  			Model:  Model{Type: "virtio"},
   129  			Source: InterfaceSource{Bridge: iface.ParentInterfaceName()},
   130  			Guest:  InterfaceGuest{Dev: iface.InterfaceName()},
   131  		})
   132  	}
   133  	return d, nil
   134  }
   135  
   136  // generateOSElement creates the architecture appropriate element details.
   137  func generateOSElement(p domainParams) OS {
   138  	switch p.Arch() {
   139  	case arch.ARM64:
   140  		return OS{
   141  			Type: OSType{
   142  				Arch:    "aarch64",
   143  				Machine: "virt",
   144  				Text:    "hvm",
   145  			},
   146  
   147  			Loader: &NVRAMCode{
   148  				Text:     p.Loader(),
   149  				ReadOnly: "yes",
   150  				Type:     "pflash",
   151  			},
   152  		}
   153  	default:
   154  		return OS{Type: OSType{Text: "hvm"}}
   155  	}
   156  }
   157  
   158  // generateFeaturesElement generates the appropriate features element based on
   159  // the architecture.
   160  func generateFeaturesElement(p domainParams) *Features {
   161  	if p.Arch() == arch.ARM64 {
   162  		return &Features{GIC: &GIC{Version: "host"}}
   163  	}
   164  	return nil
   165  }
   166  
   167  // generateCPU infor generates any model/fallback related settings. These are
   168  // typically to allow for better compatibility across versions of libvirt/qemu AFAIU.
   169  func generateCPU(p domainParams) *CPU {
   170  	if p.Arch() == arch.ARM64 {
   171  		return &CPU{
   172  			Mode:  "custom",
   173  			Match: "exact",
   174  			Model: Model{
   175  				Fallback: "allow",
   176  				Text:     "cortex-a53"},
   177  		}
   178  	}
   179  	return nil
   180  }
   181  
   182  // deviceID generates a device id from and int. The limit of 26 is arbitrary,
   183  // but it seems unlikely we'll need more than a couple for our use case.
   184  func deviceID(i int) (string, error) {
   185  	if i < 0 || i > 25 {
   186  		return "", errors.Errorf("got %d but only support devices 0-25", i)
   187  	}
   188  	return fmt.Sprintf("vd%s", string('a'+i)), nil
   189  }
   190  
   191  // Domain describes a libvirt domain. A domain is an instance of an operating
   192  // system running on a virtualized machine.
   193  // See: https://libvirt.org/formatdomain.html where we only care about kvm
   194  // specific details.
   195  type Domain struct {
   196  	XMLName       xml.Name    `xml:"domain"`
   197  	Type          string      `xml:"type,attr"`
   198  	Name          string      `xml:"name"`
   199  	VCPU          uint64      `xml:"vcpu"`
   200  	CurrentMemory Memory      `xml:"currentMemory"`
   201  	Memory        Memory      `xml:"memory"`
   202  	OS            OS          `xml:"os"`
   203  	Features      *Features   `xml:"features,omitempty"`
   204  	CPU           *CPU        `xml:"cpu,omitempty"`
   205  	Disk          []Disk      `xml:"devices>disk"`
   206  	Interface     []Interface `xml:"devices>interface"`
   207  	Serial        Serial      `xml:"devices>serial,omitempty"`
   208  	Console       []Console   `xml:"devices>console"`
   209  }
   210  
   211  // OS is static. We generate a default value (kvm) for it.
   212  // See: https://libvirt.org/formatdomain.html#elementsOSBIOS
   213  // See also: https://libvirt.org/formatcaps.html#elementGuest
   214  type OS struct {
   215  	Type OSType `xml:"type"`
   216  	// Loader is a pointer so it is omitted if empty.
   217  	Loader *NVRAMCode `xml:"loader,omitempty"`
   218  }
   219  
   220  // OSType provides details that are required on certain architectures, e.g.
   221  // ARM64.
   222  // See: https://libvirt.org/formatdomain.html#elementsOS
   223  type OSType struct {
   224  	Text    string `xml:",chardata"`
   225  	Arch    string `xml:"arch,attr,omitempty"`
   226  	Machine string `xml:"machine,attr,omitempty"`
   227  }
   228  
   229  // NVRAMCode represents the "firmware blob". In our case that is the UEFI code
   230  // which is of type pflash.
   231  // See: https://libvirt.org/formatdomain.html#elementsOS
   232  type NVRAMCode struct {
   233  	Text     string `xml:",chardata"`
   234  	ReadOnly string `xml:"readonly,attr,omitempty"`
   235  	Type     string `xml:"type,attr,omitempty"`
   236  }
   237  
   238  // Features is only generated for ARM64 at the time of this writing. This is
   239  // because GIC is required for ARM64.
   240  // See: https://libvirt.org/formatdomain.html#elementsFeatures
   241  type Features struct {
   242  	GIC *GIC `xml:"gic,omitempty"`
   243  }
   244  
   245  // GIC is the Generic Interrupt Controller and is required to UEFI boot on
   246  // ARM64.
   247  //
   248  // NB: Dann Frazier (irc:dannf) reports:
   249  // To deploy trusty, we'll either need to use a GICv2 host, or use the HWE
   250  // kernel in your guest. There are no official cloud images w/ HWE kernel
   251  // preinstalled AFAIK.
   252  // The systems we have in our #hyperscale lab are GICv3 (requiring an HWE
   253  // kernel) - but the system Juju QA has had for a while (McDivitt) is GICv2, so
   254  // it should be able to boot a standard trusty EFI cloud image.  Either way,
   255  // you'll need a xenial *host*, at least to have a new enough version of
   256  // qemu-efi and so libvirt can parse the gic_version=host xml.
   257  //
   258  // TODO(ro) 2017-01-20 Determine if we can provide details to reliably boot
   259  // trusty, or if we should exit on error if we are trying to boot trusty on
   260  // arm64.
   261  //
   262  // See: https://libvirt.org/formatdomain.html#elementsFeatures
   263  type GIC struct {
   264  	Version string `xml:"version,attr,omitempty"`
   265  }
   266  
   267  // CPU defines CPU topology and model requirements.
   268  // See: https://libvirt.org/formatdomain.html#elementsCPU
   269  type CPU struct {
   270  	Mode  string `xml:"mode,attr,omitempty"`
   271  	Match string `xml:"match,attr,omitempty"`
   272  	Model Model  `xml:"model,omitempty"`
   273  }
   274  
   275  // Address is static. We generate a default value for it.
   276  // See: Controller, Video
   277  type Address struct {
   278  	Type     string `xml:"type,attr,omitepmty"`
   279  	Domain   string `xml:"domain,attr,omitempty"`
   280  	Bus      string `xml:"bus,attr,omitempty"`
   281  	Slot     string `xml:"slot,attr,omitempty"`
   282  	Function string `xml:"function,attr,omitempty"`
   283  }
   284  
   285  // Console is static. We generate a default value for it.
   286  // See: https://libvirt.org/formatdomain.html#elementsConsole
   287  type Console struct {
   288  	Type   string        `xml:"type,attr"`
   289  	TTY    string        `xml:"tty,attr,omitempty"`
   290  	Source ConsoleSource `xml:"source,omitempty"`
   291  	Target ConsoleTarget `xml:"target,omitempty"`
   292  }
   293  
   294  // ConsoleTarget is static. We generate a default value for it.
   295  // See: Console
   296  type ConsoleTarget struct {
   297  	Type string `xml:"type,attr,omitempty"`
   298  	Port int    `xml:"port,attr"`
   299  	Path string `xml:"path,attr,omitempty"`
   300  }
   301  
   302  // ConsoleSource is static. We generate a default value for it.
   303  // See: Console
   304  type ConsoleSource struct {
   305  	Path string `xml:"path,attr,omitempty"`
   306  }
   307  
   308  // Serial is static. This was added specifically to create a functional console
   309  // for troubleshooting vms as they boot. You can attach to this console with
   310  // `virsh console <domainName>`.
   311  // See: https://libvirt.org/formatdomain.html#elementsConsole
   312  type Serial struct {
   313  	Type   string       `xml:"type,attr"`
   314  	Source SerialSource `xml:"source"`
   315  	Target SerialTarget `xml:"target"`
   316  }
   317  
   318  // SerialSource is static. We generate a default value for it.
   319  // See: Serial
   320  type SerialSource struct {
   321  	Path string `xml:"path,attr"`
   322  }
   323  
   324  // SerialTarget is static. We generate a default value for it.
   325  // See: Serial
   326  type SerialTarget struct {
   327  	Port int `xml:"port,attr"`
   328  }
   329  
   330  // Interface is dynamic. It represents a network interface. We generate it from
   331  // an incoming argument.
   332  // See: https://libvirt.org/formatdomain.html#elementsNICSBridge
   333  type Interface struct {
   334  	Type   string          `xml:"type,attr"`
   335  	MAC    InterfaceMAC    `xml:"mac"`
   336  	Model  Model           `xml:"model"`
   337  	Source InterfaceSource `xml:"source"`
   338  	Guest  InterfaceGuest  `xml:"guest"`
   339  }
   340  
   341  // InterfaceMAC is the MAC address for an Interface.
   342  // See: Interface
   343  type InterfaceMAC struct {
   344  	Address string `xml:"address,attr"`
   345  }
   346  
   347  // InterfaceSource it the host bridge to the network.
   348  // See: Interface
   349  type InterfaceSource struct {
   350  	Bridge string `xml:"bridge,attr"`
   351  }
   352  
   353  // InterfaceGuest is the guests network device.
   354  // See: Interface
   355  type InterfaceGuest struct {
   356  	Dev string `xml:"dev,attr"`
   357  }
   358  
   359  // Disk is dynamic. We create it with paths to the user data source and disk.
   360  // See: https://libvirt.org/formatdomain.html#elementsDisks
   361  type Disk struct {
   362  	Device string     `xml:"device,attr"`
   363  	Type   string     `xml:"type,attr"`
   364  	Driver DiskDriver `xml:"driver"`
   365  	Source DiskSource `xml:"source"`
   366  	Target DiskTarget `xml:"target"`
   367  }
   368  
   369  // DiskDriver is the type of virtual disk. We generate it dynamically.
   370  // See: Disk
   371  type DiskDriver struct {
   372  	Type string `xml:"type,attr"`
   373  	Name string `xml:"name,attr"`
   374  }
   375  
   376  // DiskSource is the location of the disk image. In our case the path to the
   377  // necessary images.
   378  // See: Disk
   379  type DiskSource struct {
   380  	File string `xml:"file,attr"`
   381  }
   382  
   383  // DiskTarget is the target device on the guest. We generate these.
   384  type DiskTarget struct {
   385  	Dev string `xml:"dev,attr"`
   386  }
   387  
   388  // CurrentMemory is the actual allocation of memory for the guest. It appears
   389  // we historically set this the same as Memory, which is also the default
   390  // behavior of libvirt. Constraints.Value.Mem documents this as "megabytes".
   391  // Interpreting that here as MiB.
   392  // See: Memory, github.com/juju/juju/core/constraints/constraints.Value.Mem
   393  type CurrentMemory struct {
   394  	Unit string `xml:"unit,attr"`
   395  	Text uint64 `xml:",chardata"`
   396  }
   397  
   398  // Memory is dynamic. We take an argument to set it. Unit is magnitude of
   399  // memory: b, k or KiB, KB, M or MiB, MB, etc... The libvirt default is KiB. We want to
   400  // set MiB so we default to that.
   401  // See: https://libvirt.org/formatdomain.html#elementsMemoryAllocation
   402  type Memory struct {
   403  	Unit string `xml:"unit,attr,omitempty"`
   404  	Text uint64 `xml:",chardata"`
   405  }
   406  
   407  // Model is used as an element in CPU and Interface.
   408  // See: CPU, Interface
   409  type Model struct {
   410  	Fallback string `xml:"fallback,attr,omitempty"`
   411  	Text     string `xml:",chardata"`
   412  	Type     string `xml:"type,attr,omitempty"`
   413  }