github.com/openshift/installer@v1.4.17/pkg/infrastructure/vsphere/clusterapi/import.go (about)

     1  package clusterapi
     2  
     3  import (
     4  	"context"
     5  	"crypto/sha256"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path"
    10  
    11  	"github.com/pkg/errors"
    12  	"github.com/sirupsen/logrus"
    13  	"github.com/vmware/govmomi/govc/importx"
    14  	"github.com/vmware/govmomi/nfc"
    15  	"github.com/vmware/govmomi/object"
    16  	"github.com/vmware/govmomi/ovf"
    17  	"github.com/vmware/govmomi/vim25/mo"
    18  	"github.com/vmware/govmomi/vim25/soap"
    19  	"github.com/vmware/govmomi/vim25/types"
    20  	"sigs.k8s.io/cluster-api-provider-vsphere/pkg/session"
    21  
    22  	"github.com/openshift/installer/pkg/types/vsphere"
    23  )
    24  
    25  func importRhcosOva(ctx context.Context, session *session.Session, folder *object.Folder, cachedImage, clusterID, tagID, diskProvisioningType string, failureDomain vsphere.FailureDomain) error {
    26  	name := fmt.Sprintf("%s-rhcos-%s-%s", clusterID, failureDomain.Region, failureDomain.Zone)
    27  	logrus.Infof("Importing OVA %v into failure domain %v.", name, failureDomain.Name)
    28  	archive := &importx.ArchiveFlag{Archive: &importx.TapeArchive{Path: cachedImage}}
    29  
    30  	ovfDescriptor, err := archive.ReadOvf("*.ovf")
    31  	if err != nil {
    32  		// Open the corrupt OVA file
    33  		f, ferr := os.Open(cachedImage)
    34  		if ferr != nil {
    35  			err = fmt.Errorf("%s, %w", err.Error(), ferr)
    36  		}
    37  		defer f.Close()
    38  
    39  		// Get a sha256 on the corrupt OVA file
    40  		// and the size of the file
    41  		h := sha256.New()
    42  		written, cerr := io.Copy(h, f)
    43  		if cerr != nil {
    44  			err = fmt.Errorf("%s, %w", err.Error(), cerr)
    45  		}
    46  
    47  		return fmt.Errorf("ova %s has a sha256 of %x and a size of %d bytes, failed to read the ovf descriptor %w", cachedImage, h.Sum(nil), written, err)
    48  	}
    49  
    50  	ovfEnvelope, err := archive.ReadEnvelope(ovfDescriptor)
    51  	if err != nil {
    52  		return fmt.Errorf("failed to parse ovf: %w", err)
    53  	}
    54  
    55  	// The RHCOS OVA only has one network defined by default
    56  	// The OVF envelope defines this.  We need a 1:1 mapping
    57  	// between networks with the OVF and the host
    58  	if len(ovfEnvelope.Network.Networks) != 1 {
    59  		return fmt.Errorf("expected the OVA to only have a single network adapter")
    60  	}
    61  
    62  	cluster, err := session.Finder.ClusterComputeResource(ctx, failureDomain.Topology.ComputeCluster)
    63  	if err != nil {
    64  		return fmt.Errorf("failed to find compute cluster: %w", err)
    65  	}
    66  
    67  	clusterHostSystems, err := cluster.Hosts(ctx)
    68  
    69  	if err != nil {
    70  		return fmt.Errorf("failed to get cluster hosts: %w", err)
    71  	}
    72  	resourcePool, err := session.Finder.ResourcePool(ctx, failureDomain.Topology.ResourcePool)
    73  	if err != nil {
    74  		return fmt.Errorf("failed to find resource pool: %w", err)
    75  	}
    76  
    77  	networkPath := path.Join(cluster.InventoryPath, failureDomain.Topology.Networks[0])
    78  
    79  	networkRef, err := session.Finder.Network(ctx, networkPath)
    80  	if err != nil {
    81  		return fmt.Errorf("failed to find network: %w", err)
    82  	}
    83  	datastore, err := session.Finder.Datastore(ctx, failureDomain.Topology.Datastore)
    84  	if err != nil {
    85  		return fmt.Errorf("failed to find datastore: %w", err)
    86  	}
    87  
    88  	// Create mapping between OVF and the network object
    89  	// found by Name
    90  	networkMappings := []types.OvfNetworkMapping{{
    91  		Name:    ovfEnvelope.Network.Networks[0].Name,
    92  		Network: networkRef.Reference(),
    93  	}}
    94  
    95  	// This is a very minimal spec for importing an OVF.
    96  	cisp := types.OvfCreateImportSpecParams{
    97  		EntityName:     name,
    98  		NetworkMapping: networkMappings,
    99  	}
   100  
   101  	switch diskProvisioningType {
   102  	case "":
   103  		// Disk provisioning type will be set according to the default storage policy of vsphere.
   104  	case "thin":
   105  		cisp.DiskProvisioning = string(types.OvfCreateImportSpecParamsDiskProvisioningTypeThin)
   106  	case "thick":
   107  		cisp.DiskProvisioning = string(types.OvfCreateImportSpecParamsDiskProvisioningTypeThick)
   108  	case "eagerZeroedThick":
   109  		cisp.DiskProvisioning = string(types.OvfCreateImportSpecParamsDiskProvisioningTypeEagerZeroedThick)
   110  	default:
   111  		return errors.Errorf("disk provisioning type %q is not supported", diskProvisioningType)
   112  	}
   113  
   114  	m := ovf.NewManager(session.Client.Client)
   115  	spec, err := m.CreateImportSpec(ctx,
   116  		string(ovfDescriptor),
   117  		resourcePool.Reference(),
   118  		datastore.Reference(),
   119  		cisp)
   120  
   121  	if err != nil {
   122  		return fmt.Errorf("failed to create import spec: %w", err)
   123  	}
   124  	if spec.Error != nil {
   125  		return errors.New(spec.Error[0].LocalizedMessage)
   126  	}
   127  
   128  	hostSystem, err := findAvailableHostSystems(ctx, session, clusterHostSystems)
   129  	if err != nil {
   130  		return fmt.Errorf("failed to find available host system: %w", err)
   131  	}
   132  
   133  	lease, err := resourcePool.ImportVApp(ctx, spec.ImportSpec, folder, hostSystem)
   134  
   135  	if err != nil {
   136  		return fmt.Errorf("failed to import vapp: %w", err)
   137  	}
   138  
   139  	info, err := lease.Wait(ctx, spec.FileItem)
   140  	if err != nil {
   141  		return fmt.Errorf("failed to lease wait: %w", err)
   142  	}
   143  
   144  	u := lease.StartUpdater(ctx, info)
   145  	defer u.Done()
   146  
   147  	for _, i := range info.Items {
   148  		// upload the vmdk to which ever host that was first
   149  		// available with the required network and datastore.
   150  		err = upload(ctx, archive, lease, i)
   151  		if err != nil {
   152  			return fmt.Errorf("failed to upload: %w", err)
   153  		}
   154  	}
   155  
   156  	err = lease.Complete(ctx)
   157  	if err != nil {
   158  		return fmt.Errorf("failed to lease complete: %w", err)
   159  	}
   160  
   161  	vm := object.NewVirtualMachine(session.Client.Client, info.Entity)
   162  	if vm == nil {
   163  		return fmt.Errorf("error VirtualMachine not found, managed object id: %s", info.Entity.Value)
   164  	}
   165  
   166  	err = vm.MarkAsTemplate(ctx)
   167  	if err != nil {
   168  		return fmt.Errorf("failed to mark vm as template: %w", err)
   169  	}
   170  	err = attachTag(ctx, session, vm.Reference().Value, tagID)
   171  	if err != nil {
   172  		return fmt.Errorf("failed to attach tag: %w", err)
   173  	}
   174  
   175  	return nil
   176  }
   177  
   178  func findAvailableHostSystems(ctx context.Context, session *session.Session, clusterHostSystems []*object.HostSystem) (*object.HostSystem, error) {
   179  	var hostSystemManagedObject mo.HostSystem
   180  	for _, hostObj := range clusterHostSystems {
   181  		err := hostObj.Properties(ctx, hostObj.Reference(), []string{"config.product", "network", "datastore", "runtime"}, &hostSystemManagedObject)
   182  		if err != nil {
   183  			return nil, err
   184  		}
   185  		if hostSystemManagedObject.Runtime.InMaintenanceMode {
   186  			continue
   187  		}
   188  		return hostObj, nil
   189  	}
   190  	return nil, errors.New("all hosts unavailable")
   191  }
   192  
   193  // Used govc/importx/ovf.go as an example to implement
   194  // resourceVspherePrivateImportOvaCreate and upload functions
   195  // See: https://github.com/vmware/govmomi/blob/cc10a0758d5b4d4873388bcea417251d1ad03e42/govc/importx/ovf.go#L196-L324
   196  func upload(ctx context.Context, archive *importx.ArchiveFlag, lease *nfc.Lease, item nfc.FileItem) error {
   197  	file := item.Path
   198  
   199  	f, size, err := archive.Open(file)
   200  	if err != nil {
   201  		return err
   202  	}
   203  	defer f.Close()
   204  
   205  	opts := soap.Upload{
   206  		ContentLength: size,
   207  	}
   208  
   209  	return lease.Upload(ctx, item, f, opts)
   210  }