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  }