github.com/openshift/installer@v1.4.17/pkg/infrastructure/baremetal/bootstrap.go (about) 1 package baremetal 2 3 import ( 4 "encoding/xml" 5 "errors" 6 "fmt" 7 "io" 8 "net/url" 9 "os" 10 "strings" 11 12 "github.com/digitalocean/go-libvirt" 13 "github.com/sirupsen/logrus" 14 "libvirt.org/go/libvirtxml" 15 ) 16 17 func newCopier(virConn *libvirt.Libvirt, volume libvirt.StorageVol, size uint64) func(src io.Reader) error { 18 copier := func(src io.Reader) error { 19 return virConn.StorageVolUpload(volume, src, 0, size, 0) 20 } 21 return copier 22 } 23 24 func newVolumeFromXML(s string) (libvirtxml.StorageVolume, error) { 25 var volumeDef libvirtxml.StorageVolume 26 err := xml.Unmarshal([]byte(s), &volumeDef) 27 if err != nil { 28 return libvirtxml.StorageVolume{}, err 29 } 30 return volumeDef, nil 31 } 32 33 func newDomain(name string) libvirtxml.Domain { 34 domainDef := libvirtxml.Domain{ 35 Name: name, 36 OS: &libvirtxml.DomainOS{ 37 Type: &libvirtxml.DomainOSType{ 38 Type: "hvm", 39 }, 40 }, 41 Devices: &libvirtxml.DomainDeviceList{ 42 Graphics: []libvirtxml.DomainGraphic{ 43 { 44 Spice: &libvirtxml.DomainGraphicSpice{ 45 AutoPort: "yes", 46 }, 47 }, 48 }, 49 Channels: []libvirtxml.DomainChannel{ 50 { 51 Source: &libvirtxml.DomainChardevSource{ 52 UNIX: &libvirtxml.DomainChardevSourceUNIX{}, 53 }, 54 Target: &libvirtxml.DomainChannelTarget{ 55 VirtIO: &libvirtxml.DomainChannelTargetVirtIO{ 56 Name: "org.qemu.guest_agent.0", 57 }, 58 }, 59 }, 60 }, 61 }, 62 Features: &libvirtxml.DomainFeatureList{ 63 PAE: &libvirtxml.DomainFeature{}, 64 ACPI: &libvirtxml.DomainFeature{}, 65 APIC: &libvirtxml.DomainFeatureAPIC{}, 66 }, 67 68 CPU: &libvirtxml.DomainCPU{ 69 Mode: "host-passthrough", 70 }, 71 Memory: &libvirtxml.DomainMemory{ 72 Value: 6144, 73 Unit: "MiB", 74 }, 75 VCPU: &libvirtxml.DomainVCPU{ 76 Value: 4, 77 }, 78 } 79 80 targetPort := uint(0) 81 console := libvirtxml.DomainConsole{ 82 Target: &libvirtxml.DomainConsoleTarget{ 83 Port: &targetPort, 84 }, 85 } 86 87 domainDef.Devices.Consoles = append(domainDef.Devices.Consoles, console) 88 89 domainDef.Devices.Graphics = []libvirtxml.DomainGraphic{ 90 { 91 VNC: &libvirtxml.DomainGraphicVNC{ 92 AutoPort: "yes", 93 Listeners: []libvirtxml.DomainGraphicListener{ 94 { 95 Address: &libvirtxml.DomainGraphicListenerAddress{ 96 Address: "", 97 }, 98 }, 99 }, 100 }, 101 }, 102 } 103 104 if v := os.Getenv("TERRAFORM_LIBVIRT_TEST_DOMAIN_TYPE"); v != "" { 105 domainDef.Type = v 106 } else { 107 domainDef.Type = "kvm" 108 } 109 110 rngDev := os.Getenv("TF_LIBVIRT_RNG_DEV") 111 if rngDev == "" { 112 rngDev = "/dev/urandom" 113 } 114 115 domainDef.Devices.RNGs = []libvirtxml.DomainRNG{ 116 { 117 Model: "virtio", 118 Backend: &libvirtxml.DomainRNGBackend{ 119 Random: &libvirtxml.DomainRNGBackendRandom{Device: rngDev}, 120 }, 121 }, 122 } 123 124 return domainDef 125 } 126 127 func newVolume(name string) libvirtxml.StorageVolume { 128 return libvirtxml.StorageVolume{ 129 Name: name, 130 Target: &libvirtxml.StorageVolumeTarget{ 131 Format: &libvirtxml.StorageVolumeTargetFormat{ 132 Type: "qcow2", 133 }, 134 Permissions: &libvirtxml.StorageVolumeTargetPermissions{ 135 Mode: "644", 136 }, 137 }, 138 Capacity: &libvirtxml.StorageVolumeSize{ 139 Unit: "bytes", 140 Value: 1, 141 }, 142 } 143 } 144 145 func createStoragePool(virConn *libvirt.Libvirt, config baremetalConfig) (libvirt.StoragePool, error) { 146 // TODO: check if unique 147 bootstrapPool := libvirtxml.StoragePool{ 148 Type: "dir", 149 Name: fmt.Sprintf("%s-bootstrap", config.ClusterID), 150 Target: &libvirtxml.StoragePoolTarget{ 151 Path: fmt.Sprintf("/var/lib/libvirt/openshift-images/%s-bootstrap", config.ClusterID), 152 }, 153 } 154 155 bootstrapPoolXML, err := xml.Marshal(bootstrapPool) 156 if err != nil { 157 return libvirt.StoragePool{}, err 158 } 159 160 pool, err := virConn.StoragePoolDefineXML(string(bootstrapPoolXML), 0) 161 if err != nil { 162 return libvirt.StoragePool{}, err 163 } 164 165 err = virConn.StoragePoolBuild(pool, libvirt.StoragePoolBuildNew) 166 if err != nil { 167 return libvirt.StoragePool{}, err 168 } 169 170 err = virConn.StoragePoolSetAutostart(pool, 1) 171 if err != nil { 172 return libvirt.StoragePool{}, err 173 } 174 175 err = virConn.StoragePoolCreate(pool, libvirt.StoragePoolCreateNormal) 176 if err != nil { 177 return libvirt.StoragePool{}, err 178 } 179 180 err = virConn.StoragePoolRefresh(pool, 0) 181 if err != nil { 182 return libvirt.StoragePool{}, err 183 } 184 return pool, nil 185 } 186 187 func createBaseVolume(virConn *libvirt.Libvirt, config baremetalConfig, pool libvirt.StoragePool) (libvirt.StorageVol, error) { 188 bootstrapBaseVolume := newVolume(fmt.Sprintf("%s-bootstrap-base", config.ClusterID)) 189 image, err := newImage(config.BootstrapOSImage) 190 if err != nil { 191 return libvirt.StorageVol{}, err 192 } 193 194 isQCOW2, err := image.IsQCOW2() 195 if err != nil { 196 return libvirt.StorageVol{}, err 197 } 198 if isQCOW2 { 199 bootstrapBaseVolume.Target.Format.Type = "qcow2" 200 } 201 202 size, err := image.Size() 203 if err != nil { 204 return libvirt.StorageVol{}, err 205 } 206 207 bootstrapBaseVolume.Capacity.Unit = "B" 208 bootstrapBaseVolume.Capacity.Value = size 209 210 bootstrapBaseVolumeXML, err := xml.Marshal(bootstrapBaseVolume) 211 if err != nil { 212 return libvirt.StorageVol{}, err 213 } 214 215 baseVolume, err := virConn.StorageVolCreateXML(pool, string(bootstrapBaseVolumeXML), 0) 216 217 if err != nil { 218 return libvirt.StorageVol{}, err 219 } 220 221 err = image.Import(newCopier(virConn, baseVolume, bootstrapBaseVolume.Capacity.Value), bootstrapBaseVolume) 222 if err != nil { 223 return libvirt.StorageVol{}, err 224 } 225 226 return baseVolume, nil 227 } 228 229 func createMainVolume(virConn *libvirt.Libvirt, config baremetalConfig, pool libvirt.StoragePool, baseVolume libvirt.StorageVol) (libvirt.StorageVol, error) { 230 bootstrapVolume := newVolume(fmt.Sprintf("%s-bootstrap", config.ClusterID)) 231 bootstrapVolume.Capacity.Value = 34359738368 232 233 volPath, err := virConn.StorageVolGetPath(baseVolume) 234 if err != nil { 235 return libvirt.StorageVol{}, err 236 } 237 238 baseVolumeXMLDesc, err := virConn.StorageVolGetXMLDesc(baseVolume, 0) 239 if err != nil { 240 return libvirt.StorageVol{}, err 241 } 242 243 baseVolFromLibvirt, err := newVolumeFromXML(baseVolumeXMLDesc) 244 if err != nil { 245 return libvirt.StorageVol{}, err 246 } 247 248 backingStoreVolumeDef := libvirtxml.StorageVolumeBackingStore{ 249 Path: volPath, 250 Format: baseVolFromLibvirt.Target.Format, 251 } 252 253 bootstrapVolume.BackingStore = &backingStoreVolumeDef 254 255 bootstrapVolumeXML, err := xml.Marshal(bootstrapVolume) 256 if err != nil { 257 return libvirt.StorageVol{}, err 258 } 259 260 return virConn.StorageVolCreateXML(pool, string(bootstrapVolumeXML), 0) 261 } 262 func createIgnition(virConn *libvirt.Libvirt, config baremetalConfig, pool libvirt.StoragePool) error { 263 bootstrapIgnition := defIgnition{ 264 Name: fmt.Sprintf("%s-bootstrap.ign", config.ClusterID), 265 PoolName: pool.Name, 266 Content: config.IgnitionBootstrap, 267 } 268 269 _, err := bootstrapIgnition.CreateAndUpload(virConn) 270 if err != nil { 271 return err 272 } 273 274 return nil 275 } 276 277 func getHostCapabilities(virConn *libvirt.Libvirt) (libvirtxml.Caps, error) { 278 var caps libvirtxml.Caps 279 280 capsBytes, err := virConn.Capabilities() 281 if err != nil { 282 return caps, err 283 } 284 285 err = xml.Unmarshal(capsBytes, &caps) 286 if err != nil { 287 return caps, err 288 } 289 290 return caps, nil 291 } 292 293 func createBootstrapDomain(virConn *libvirt.Libvirt, config baremetalConfig, pool libvirt.StoragePool, volume libvirt.StorageVol) error { 294 bootstrapDom := newDomain(fmt.Sprintf("%s-bootstrap", config.ClusterID)) 295 296 capabilities, err := getHostCapabilities(virConn) 297 if err != nil { 298 return fmt.Errorf("failed to get libvirt capabilities: %w", err) 299 } 300 301 arch := capabilities.Host.CPU.Arch 302 bootstrapDom.OS.Type.Arch = arch 303 304 if arch == "aarch64" { 305 // for aarch64 speciffying this will automatically select the firmware and NVRAM file 306 // reference: https://libvirt.org/formatdomain.html#bios-bootloader 307 bootstrapDom.OS.Firmware = "efi" 308 } 309 310 // For aarch64, s390x, ppc64 and ppc64le spice is not supported 311 if arch == "aarch64" || arch == "s390x" || strings.HasPrefix(arch, "ppc64") { 312 bootstrapDom.Devices.Graphics = nil 313 } 314 315 for _, bridge := range config.Bridges { 316 netIface := libvirtxml.DomainInterface{ 317 Model: &libvirtxml.DomainInterfaceModel{ 318 Type: "virtio", 319 }, 320 MAC: &libvirtxml.DomainInterfaceMAC{ 321 Address: bridge.MAC, 322 }, 323 Source: &libvirtxml.DomainInterfaceSource{ 324 Bridge: &libvirtxml.DomainInterfaceSourceBridge{ 325 Bridge: bridge.Name, 326 }, 327 }, 328 } 329 330 bootstrapDom.Devices.Interfaces = append(bootstrapDom.Devices.Interfaces, netIface) 331 } 332 333 disk := libvirtxml.DomainDisk{ 334 Device: "disk", 335 Target: &libvirtxml.DomainDiskTarget{ 336 Bus: "virtio", 337 Dev: "vda", 338 }, 339 Driver: &libvirtxml.DomainDiskDriver{ 340 Name: "qemu", 341 Type: "raw", 342 }, 343 Source: &libvirtxml.DomainDiskSource{ 344 Index: 0, 345 Volume: &libvirtxml.DomainDiskSourceVolume{ 346 Pool: pool.Name, 347 Volume: volume.Name, 348 }, 349 }, 350 } 351 352 disk.Driver = &libvirtxml.DomainDiskDriver{ 353 Name: "qemu", 354 Type: "qcow2", 355 } 356 bootstrapDom.Devices.Disks = append(bootstrapDom.Devices.Disks, disk) 357 358 ignitionKey := fmt.Sprintf("/var/lib/libvirt/openshift-images/%s-bootstrap/%s-bootstrap.ign", config.ClusterID, config.ClusterID) 359 bootstrapDom.QEMUCommandline = &libvirtxml.DomainQEMUCommandline{ 360 Args: []libvirtxml.DomainQEMUCommandlineArg{ 361 { 362 Value: "-fw_cfg", 363 }, 364 { 365 Value: fmt.Sprintf("name=%s,file=%s", "opt/com.coreos/config", ignitionKey), 366 }, 367 }, 368 } 369 370 bootstrapDom.Resource = nil 371 372 bootstrapDomXML, err := xml.Marshal(bootstrapDom) 373 if err != nil { 374 return err 375 } 376 377 dom, err := virConn.DomainDefineXML(string(bootstrapDomXML)) 378 if err != nil { 379 return err 380 } 381 382 err = virConn.DomainCreate(dom) 383 if err != nil { 384 return err 385 } 386 387 return nil 388 } 389 390 func createBootstrap(config baremetalConfig) error { 391 logrus.Debug("libvirt: Creating bootstrap") 392 uri, err := url.Parse(config.LibvirtURI) 393 if err != nil { 394 return err 395 } 396 397 virConn, err := libvirt.ConnectToURI(uri) 398 if err != nil { 399 return err 400 } 401 402 logrus.Debug(" Creating storage pool") 403 pool, err := createStoragePool(virConn, config) 404 if err != nil { 405 return err 406 } 407 408 logrus.Debug(" Creating base volume") 409 baseVolume, err := createBaseVolume(virConn, config, pool) 410 if err != nil { 411 return err 412 } 413 414 logrus.Debug(" Creating main volume") 415 mainVolume, err := createMainVolume(virConn, config, pool, baseVolume) 416 if err != nil { 417 return err 418 } 419 420 logrus.Debug(" Creating ignition") 421 err = createIgnition(virConn, config, pool) 422 if err != nil { 423 return err 424 } 425 426 logrus.Debug(" Creating bootstrap domain") 427 err = createBootstrapDomain(virConn, config, pool, mainVolume) 428 if err != nil { 429 return err 430 } 431 432 return nil 433 } 434 435 func destroyBootstrap(config baremetalConfig) error { 436 logrus.Debug("libvirt: Destroying bootstrap") 437 438 uri, err := url.Parse(config.LibvirtURI) 439 if err != nil { 440 return err 441 } 442 443 virConn, err := libvirt.ConnectToURI(uri) 444 if err != nil { 445 return err 446 } 447 448 name := fmt.Sprintf("%s-bootstrap", config.ClusterID) 449 450 dom, err := virConn.DomainLookupByName(name) 451 if err != nil { 452 return err 453 } 454 455 logrus.Debug(" Destroying domain") 456 err = virConn.DomainDestroy(dom) 457 if err != nil { 458 return err 459 } 460 461 logrus.Debug(" Undefining domain") 462 463 if err := virConn.DomainUndefineFlags(dom, libvirt.DomainUndefineNvram|libvirt.DomainUndefineSnapshotsMetadata|libvirt.DomainUndefineManagedSave|libvirt.DomainUndefineCheckpointsMetadata); err != nil { 464 var libvirtErr *libvirt.Error 465 466 if !errors.As(err, &libvirtErr) { 467 return fmt.Errorf("failed to cast to libvirt.Error: %w", err) 468 } 469 470 if libvirtErr.Code == uint32(libvirt.ErrNoSupport) || libvirtErr.Code == uint32(libvirt.ErrInvalidArg) { 471 logrus.Printf("libvirt does not support undefine flags: will try again without flags") 472 if err := virConn.DomainUndefine(dom); err != nil { 473 return fmt.Errorf("couldn't undefine libvirt domain: %w", err) 474 } 475 } else { 476 return fmt.Errorf("couldn't undefine libvirt domain with flags: %w", err) 477 } 478 } 479 480 pool, err := virConn.StoragePoolLookupByName(name) 481 if err != nil { 482 return err 483 } 484 485 vol, err := virConn.StorageVolLookupByName(pool, name) 486 if err != nil { 487 return err 488 } 489 490 logrus.Debug(" Deleting main volume") 491 err = virConn.StorageVolDelete(vol, libvirt.StorageVolDeleteNormal) 492 if err != nil { 493 return err 494 } 495 496 vol, err = virConn.StorageVolLookupByName(pool, fmt.Sprintf("%s-bootstrap-base", config.ClusterID)) 497 if err != nil { 498 return err 499 } 500 501 logrus.Debug(" Deleting base volume") 502 err = virConn.StorageVolDelete(vol, libvirt.StorageVolDeleteNormal) 503 if err != nil { 504 return err 505 } 506 507 vol, err = virConn.StorageVolLookupByName(pool, fmt.Sprintf("%s-bootstrap.ign", config.ClusterID)) 508 if err != nil { 509 return err 510 } 511 512 logrus.Debug(" Deleting ignition volume") 513 err = virConn.StorageVolDelete(vol, libvirt.StorageVolDeleteNormal) 514 if err != nil { 515 return err 516 } 517 518 logrus.Debug(" Destroying pool") 519 err = virConn.StoragePoolDestroy(pool) 520 if err != nil { 521 return err 522 } 523 524 logrus.Debug(" Deleting pool pool") 525 err = virConn.StoragePoolDelete(pool, libvirt.StoragePoolDeleteNormal) 526 if err != nil { 527 return err 528 } 529 530 return nil 531 }