github.com/vmware/govmomi@v0.37.2/simulator/ovf_manager.go (about)

     1  /*
     2  Copyright (c) 2019-2023 VMware, Inc. All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package simulator
    18  
    19  import (
    20  	"fmt"
    21  	"log"
    22  	"math"
    23  	"strconv"
    24  	"strings"
    25  
    26  	"github.com/vmware/govmomi/object"
    27  	"github.com/vmware/govmomi/ovf"
    28  	"github.com/vmware/govmomi/simulator/esx"
    29  	"github.com/vmware/govmomi/vim25/methods"
    30  	"github.com/vmware/govmomi/vim25/mo"
    31  	"github.com/vmware/govmomi/vim25/soap"
    32  	"github.com/vmware/govmomi/vim25/types"
    33  )
    34  
    35  type OvfManager struct {
    36  	mo.OvfManager
    37  }
    38  
    39  func ovfDisk(e *ovf.Envelope, diskID string) *ovf.VirtualDiskDesc {
    40  	for _, disk := range e.Disk.Disks {
    41  		if strings.HasSuffix(diskID, disk.DiskID) {
    42  			return &disk
    43  		}
    44  	}
    45  	return nil
    46  }
    47  
    48  func ovfNetwork(ctx *Context, req *types.CreateImportSpec, item ovf.ResourceAllocationSettingData) types.BaseVirtualDeviceBackingInfo {
    49  	if len(item.Connection) == 0 {
    50  		return nil
    51  	}
    52  	pool := ctx.Map.Get(req.ResourcePool).(mo.Entity)
    53  	ref := ctx.Map.getEntityDatacenter(pool).defaultNetwork()[0] // Default to VM Network
    54  	c := item.Connection[0]
    55  
    56  	for _, net := range req.Cisp.NetworkMapping {
    57  		if net.Name == c {
    58  			ref = net.Network
    59  			break
    60  		}
    61  	}
    62  
    63  	switch obj := ctx.Map.Get(ref).(type) {
    64  	case *mo.Network:
    65  		return &types.VirtualEthernetCardNetworkBackingInfo{
    66  			VirtualDeviceDeviceBackingInfo: types.VirtualDeviceDeviceBackingInfo{
    67  				DeviceName: obj.Name,
    68  			},
    69  		}
    70  	case *DistributedVirtualPortgroup:
    71  		dvs := ctx.Map.Get(*obj.Config.DistributedVirtualSwitch).(*DistributedVirtualSwitch)
    72  		return &types.VirtualEthernetCardDistributedVirtualPortBackingInfo{
    73  			Port: types.DistributedVirtualSwitchPortConnection{
    74  				PortgroupKey: obj.Key,
    75  				SwitchUuid:   dvs.Config.GetDVSConfigInfo().Uuid,
    76  			},
    77  		}
    78  	default:
    79  		log.Printf("ovf: unknown network type: %T", ref)
    80  		return nil
    81  	}
    82  }
    83  
    84  func ovfDiskCapacity(disk *ovf.VirtualDiskDesc) int64 {
    85  	b, _ := strconv.ParseUint(disk.Capacity, 10, 64)
    86  	if disk.CapacityAllocationUnits == nil {
    87  		return int64(b)
    88  	}
    89  	c := strings.Fields(*disk.CapacityAllocationUnits)
    90  	if len(c) == 3 && c[0] == "byte" && c[1] == "*" { // "byte * 2^20"
    91  		p := strings.Split(c[2], "^")
    92  		x, _ := strconv.ParseUint(p[0], 10, 64)
    93  		if len(p) == 2 {
    94  			y, _ := strconv.ParseUint(p[1], 10, 64)
    95  			b *= uint64(math.Pow(float64(x), float64(y)))
    96  		} else {
    97  			b *= x
    98  		}
    99  	}
   100  	return int64(b / 1024)
   101  }
   102  
   103  func (m *OvfManager) CreateImportSpec(ctx *Context, req *types.CreateImportSpec) soap.HasFault {
   104  	body := new(methods.CreateImportSpecBody)
   105  
   106  	env, err := ovf.Unmarshal(strings.NewReader(req.OvfDescriptor))
   107  	if err != nil {
   108  		body.Fault_ = Fault(err.Error(), &types.InvalidArgument{InvalidProperty: "ovfDescriptor"})
   109  		return body
   110  	}
   111  
   112  	ds := ctx.Map.Get(req.Datastore).(*Datastore)
   113  	path := object.DatastorePath{Datastore: ds.Name}
   114  	vapp := &types.VAppConfigSpec{}
   115  	spec := &types.VirtualMachineImportSpec{
   116  		ConfigSpec: types.VirtualMachineConfigSpec{
   117  			Name:    req.Cisp.EntityName,
   118  			Version: esx.HardwareVersion,
   119  			GuestId: string(types.VirtualMachineGuestOsIdentifierOtherGuest),
   120  			Files: &types.VirtualMachineFileInfo{
   121  				VmPathName: path.String(),
   122  			},
   123  			NumCPUs:           1,
   124  			NumCoresPerSocket: 1,
   125  			MemoryMB:          32,
   126  			VAppConfig:        vapp,
   127  		},
   128  		ResPoolEntity: &req.ResourcePool,
   129  	}
   130  
   131  	index := 0
   132  	for i, product := range env.VirtualSystem.Product {
   133  		vapp.Product = append(vapp.Product, types.VAppProductSpec{
   134  			ArrayUpdateSpec: types.ArrayUpdateSpec{
   135  				Operation: types.ArrayUpdateOperationAdd,
   136  			},
   137  			Info: &types.VAppProductInfo{
   138  				Key:        int32(i),
   139  				ClassId:    toString(product.Class),
   140  				InstanceId: toString(product.Instance),
   141  				Name:       product.Product,
   142  				Vendor:     product.Vendor,
   143  				Version:    product.Version,
   144  			},
   145  		})
   146  
   147  		for _, p := range product.Property {
   148  			key := product.Key(p)
   149  			val := ""
   150  
   151  			for _, m := range req.Cisp.PropertyMapping {
   152  				if m.Key == key {
   153  					val = m.Value
   154  				}
   155  			}
   156  
   157  			vapp.Property = append(vapp.Property, types.VAppPropertySpec{
   158  				ArrayUpdateSpec: types.ArrayUpdateSpec{
   159  					Operation: types.ArrayUpdateOperationAdd,
   160  				},
   161  				Info: &types.VAppPropertyInfo{
   162  					Key:              int32(index),
   163  					ClassId:          toString(product.Class),
   164  					InstanceId:       toString(product.Instance),
   165  					Id:               p.Key,
   166  					Category:         product.Category,
   167  					Label:            toString(p.Label),
   168  					Type:             p.Type,
   169  					UserConfigurable: p.UserConfigurable,
   170  					DefaultValue:     toString(p.Default),
   171  					Value:            val,
   172  					Description:      toString(p.Description),
   173  				},
   174  			})
   175  			index++
   176  		}
   177  	}
   178  
   179  	if req.Cisp.DeploymentOption == "" && env.DeploymentOption != nil {
   180  		for _, c := range env.DeploymentOption.Configuration {
   181  			if isTrue(c.Default) {
   182  				req.Cisp.DeploymentOption = c.ID
   183  				break
   184  			}
   185  		}
   186  	}
   187  
   188  	if os := env.VirtualSystem.OperatingSystem; len(os) != 0 {
   189  		if id := os[0].OSType; id != nil {
   190  			spec.ConfigSpec.GuestId = *id
   191  		}
   192  	}
   193  
   194  	var device object.VirtualDeviceList
   195  	result := types.OvfCreateImportSpecResult{
   196  		ImportSpec: spec,
   197  	}
   198  
   199  	hw := env.VirtualSystem.VirtualHardware[0]
   200  	if vmx := hw.System.VirtualSystemType; vmx != nil {
   201  		spec.ConfigSpec.Version = *vmx
   202  	}
   203  
   204  	ndisk := 0
   205  	ndev := 0
   206  	resources := make(map[string]types.BaseVirtualDevice)
   207  
   208  	for _, item := range hw.Item {
   209  		if req.Cisp.DeploymentOption != "" && item.Configuration != nil {
   210  			if req.Cisp.DeploymentOption != *item.Configuration {
   211  				continue
   212  			}
   213  		}
   214  
   215  		kind := func() string {
   216  			if item.ResourceSubType == nil {
   217  				return "unknown"
   218  			}
   219  			return strings.ToLower(*item.ResourceSubType)
   220  		}
   221  
   222  		unsupported := func(err error) {
   223  			result.Error = append(result.Error, types.LocalizedMethodFault{
   224  				Fault: &types.OvfUnsupportedType{
   225  					Name:       item.ElementName,
   226  					InstanceId: item.InstanceID,
   227  					DeviceType: int32(*item.ResourceType),
   228  				},
   229  				LocalizedMessage: err.Error(),
   230  			})
   231  		}
   232  
   233  		upload := func(file ovf.File, c types.BaseVirtualDevice, n int) {
   234  			result.FileItem = append(result.FileItem, types.OvfFileItem{
   235  				DeviceId: fmt.Sprintf("/%s/%s:%d", req.Cisp.EntityName, device.Type(c), n),
   236  				Path:     file.Href,
   237  				Size:     int64(file.Size),
   238  				CimType:  int32(*item.ResourceType),
   239  			})
   240  		}
   241  
   242  		switch *item.ResourceType {
   243  		case 1: // VMCI
   244  		case 3: // Number of Virtual CPUs
   245  			spec.ConfigSpec.NumCPUs = int32(*item.VirtualQuantity)
   246  		case 4: // Memory Size
   247  			spec.ConfigSpec.MemoryMB = int64(*item.VirtualQuantity)
   248  		case 5: // IDE Controller
   249  			d, _ := device.CreateIDEController()
   250  			device = append(device, d)
   251  			resources[item.InstanceID] = d
   252  		case 6: // SCSI Controller
   253  			d, err := device.CreateSCSIController(kind())
   254  			if err == nil {
   255  				device = append(device, d)
   256  				resources[item.InstanceID] = d
   257  			} else {
   258  				unsupported(err)
   259  			}
   260  		case 10: // Virtual Network
   261  			net := ovfNetwork(ctx, req, item)
   262  			if net != nil {
   263  				d, err := device.CreateEthernetCard(kind(), net)
   264  				if err == nil {
   265  					device = append(device, d)
   266  				} else {
   267  					unsupported(err)
   268  				}
   269  			}
   270  		case 14: // Floppy Drive
   271  			if device.PickController((*types.VirtualSIOController)(nil)) == nil {
   272  				c := &types.VirtualSIOController{}
   273  				c.Key = device.NewKey()
   274  				device = append(device, c)
   275  			}
   276  			d, err := device.CreateFloppy()
   277  			if err == nil {
   278  				device = append(device, d)
   279  				resources[item.InstanceID] = d
   280  			} else {
   281  				unsupported(err)
   282  			}
   283  		case 15: // CD/DVD
   284  			c, ok := resources[*item.Parent]
   285  			if !ok {
   286  				continue // Parent is unsupported()
   287  			}
   288  			d, _ := device.CreateCdrom(c.(*types.VirtualIDEController))
   289  			if len(item.HostResource) != 0 {
   290  				for _, file := range env.References {
   291  					if strings.HasSuffix(item.HostResource[0], file.ID) {
   292  						path.Path = fmt.Sprintf("%s/_deviceImage%d.iso", req.Cisp.EntityName, ndev)
   293  						device.InsertIso(d, path.String())
   294  						upload(file, d, ndev)
   295  						break
   296  					}
   297  				}
   298  			}
   299  			device = append(device, d)
   300  			ndev++
   301  		case 17: // Virtual Disk
   302  			c, ok := resources[*item.Parent]
   303  			if !ok {
   304  				continue // Parent is unsupported()
   305  			}
   306  			path.Path = fmt.Sprintf("%s/disk-%d.vmdk", req.Cisp.EntityName, ndisk)
   307  			d := device.CreateDisk(c.(types.BaseVirtualController), ds.Reference(), path.String())
   308  			d.VirtualDevice.DeviceInfo = &types.Description{
   309  				Label: item.ElementName,
   310  			}
   311  			disk := ovfDisk(env, item.HostResource[0])
   312  			for _, file := range env.References {
   313  				if file.ID == *disk.FileRef {
   314  					upload(file, d, ndisk)
   315  					break
   316  				}
   317  			}
   318  			d.CapacityInKB = ovfDiskCapacity(disk)
   319  			device = append(device, d)
   320  			ndisk++
   321  		case 23: // USB Controller
   322  		case 24: // Video Card
   323  		default:
   324  			unsupported(fmt.Errorf("unsupported resource type: %d", *item.ResourceType))
   325  		}
   326  	}
   327  
   328  	spec.ConfigSpec.DeviceChange, _ = device.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
   329  
   330  	for _, p := range req.Cisp.PropertyMapping {
   331  		spec.ConfigSpec.ExtraConfig = append(spec.ConfigSpec.ExtraConfig, &types.OptionValue{
   332  			Key:   p.Key,
   333  			Value: p.Value,
   334  		})
   335  	}
   336  
   337  	body.Res = &types.CreateImportSpecResponse{
   338  		Returnval: result,
   339  	}
   340  
   341  	return body
   342  }