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 }