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 }