Skip to content
This repository was archived by the owner on Mar 6, 2024. It is now read-only.

Commit 23388db

Browse files
authored
Fix: vApp instantiation fails if template has more than one VM (#713)
There is a bug where vApp instantiation fails if the template has multiple VMs and any of VMs has more than one NICs. And, any of NIC is connected to a vDC network. It works fine if VMs are only connected to vApp networks. Existing API was designed on an assumption that there will be only one VM having one NIC. Restructured the code to handle multiple VMs. Behavior will remain same for a single VM template. In case of multiple VMs, customization parameters will be ignored. Testing Done: Ran vapp_tests.py, related tests are passing. Also did manual testing of following scenarions: - Single VM template with one NIC and no customization - Single VM template with one NIC and network customization - Two VMs template with two NICs, one conntected to a vDC network and other to a vApp network
1 parent 194351d commit 23388db

File tree

1 file changed

+181
-154
lines changed

1 file changed

+181
-154
lines changed

pyvcloud/vcd/vdc.py

Lines changed: 181 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,13 @@ def instantiate_vapp(self,
241241
If customization parameters are provided, it will customize the vm and
242242
guest OS, taking some assumptions.
243243
244-
A general assumption is made by this method that there is only one vm
245-
in the vApp. And the vm has only one NIC.
244+
A general assumption is made by this method that customization
245+
parameters are applicable only if there is only one vm in the vApp.
246+
And, the vm has a single NIC.
247+
248+
In case of a vApp template having multiple VMs having multiple NICs in
249+
any of the VM, none of the optional paramters will be considered. vApp
250+
template will be instantiated with default settings.
246251
247252
:param str name: name of the new vApp.
248253
:param str catalog: name of the catalog.
@@ -296,103 +301,190 @@ def instantiate_vapp(self,
296301
template_resource = self.client.get_resource(
297302
catalog_item.Entity.get('href'))
298303

299-
# If network is not specified by user then default to
300-
# vApp network name specified in the template
301-
is_template_vapp_network = False
302-
template_networks = template_resource.xpath(
303-
'//ovf:NetworkSection/ovf:Network',
304-
namespaces={'ovf': NSMAP['ovf']})
305-
# assert len(template_networks) > 0
306-
if len(template_networks) > 0:
307-
network_name_from_template = template_networks[0].get(
308-
'{' + NSMAP['ovf'] + '}name')
309-
if ((network is None) and (network_name_from_template != 'none')):
310-
network = network_name_from_template
311-
is_template_vapp_network = True
312-
313-
# Find the network in vdc referred to by user, using
314-
# name of the network, ignoring template vApp networks
315-
network_href = network_name = None
316-
if network is not None:
317-
if hasattr(self.resource, 'AvailableNetworks') and \
318-
hasattr(self.resource.AvailableNetworks, 'Network'):
319-
for n in self.resource.AvailableNetworks.Network:
320-
if network == n.get('name'):
321-
network_href = n.get('href')
322-
network_name = n.get('name')
323-
break
324-
if network_href is None:
325-
if is_template_vapp_network:
326-
pass
327-
else:
328-
raise EntityNotFoundException(
329-
'Network \'%s\' not found in the Virtual Datacenter.' %
330-
network)
331-
332-
# Configure the network of the vApp
333-
vapp_instantiation_param = None
334-
if network_name is not None:
335-
network_configuration = E.Configuration(
336-
E.ParentNetwork(href=network_href), E.FenceMode(fence_mode))
337-
338-
if fence_mode == 'natRouted':
339-
# TODO(need to find the vm_id)
340-
vm_id = None
341-
network_configuration.append(
342-
E.Features(
343-
E.NatService(
344-
E.IsEnabled('true'), E.NatType('ipTranslation'),
345-
E.Policy('allowTraffic'),
346-
E.NatRule(
347-
E.OneToOneVmRule(
348-
E.MappingMode('automatic'),
349-
E.VAppScopedVmId(vm_id), E.VmNicId(0))))))
350-
351-
vapp_instantiation_param = E.InstantiationParams(
352-
E.NetworkConfigSection(
353-
E_OVF.Info('Configuration for logical networks'),
354-
E.NetworkConfig(
355-
network_configuration, networkName=network_name)))
356-
357304
# Get all vms in the vapp template
358305
vms = template_resource.xpath(
359306
'//vcloud:VAppTemplate/vcloud:Children/vcloud:Vm',
360307
namespaces=NSMAP)
361308
assert len(vms) > 0
362309

363-
vm_instantiation_param = E.InstantiationParams()
310+
if len(vms) > 1:
311+
# Reset any customization parameters if provided
312+
network = memory = cpu = disk_size = password = cust_script = \
313+
vm_name = hostname = ip_address = storage_profile = \
314+
network_adapter_type = None
315+
316+
template_vdc_networks = []
317+
# If provided, only given network will be configured
318+
if network is not None:
319+
template_vdc_networks.append(network)
320+
# Get all vDC networks from vapp template
321+
elif hasattr(template_resource, 'NetworkConfigSection') and hasattr(
322+
template_resource.NetworkConfigSection, 'NetworkConfig'):
323+
for net in template_resource.NetworkConfigSection\
324+
.NetworkConfig:
325+
if hasattr(net.Configuration, 'ParentNetwork'):
326+
template_vdc_networks.append(net.get('networkName'))
327+
328+
vdc_networks = {}
329+
# Get all networks in vDC
330+
if hasattr(self.resource, 'AvailableNetworks') and hasattr(
331+
self.resource.AvailableNetworks, 'Network'):
332+
for net in self.resource.AvailableNetworks.Network:
333+
vdc_networks[net.get('name')] = net.get('href')
334+
335+
# Check if networks used in vapp template are found in vDC
336+
for net in template_vdc_networks:
337+
if net not in vdc_networks:
338+
raise EntityNotFoundException(
339+
'Network \'%s\' not found in the Virtual Datacenter.' %
340+
net)
341+
342+
vapp_instantiation_param = E.InstantiationParams()
343+
if network is not None:
344+
self._configure_vapp_network(vapp_instantiation_param, network,
345+
vdc_networks.get(network), fence_mode)
346+
347+
# Cook the entire vApp Template instantiation element
348+
deploy_param = 'true' if deploy else 'false'
349+
power_on_param = 'true' if power_on else 'false'
350+
all_eulas_accepted = 'true' if accept_all_eulas else 'false'
351+
352+
vapp_template_params = E.InstantiateVAppTemplateParams(
353+
name=name, deploy=deploy_param, powerOn=power_on_param)
354+
355+
if description is not None:
356+
vapp_template_params.append(E.Description(description))
357+
358+
if vapp_instantiation_param is not None:
359+
vapp_template_params.append(vapp_instantiation_param)
360+
361+
vapp_template_params.append(
362+
E.Source(href=catalog_item.Entity.get('href')))
363+
364+
for vm in vms:
365+
vm_instantiation_param = E.InstantiationParams()
366+
367+
# Configure network of vms
368+
self._configure_vm_netowrk(vm_instantiation_param, vm, network,
369+
ip_address, ip_allocation_mode,
370+
network_adapter_type)
371+
372+
# Configure cpu, memory, disk of the first vm
373+
self._configure_vm_compute(vm_instantiation_param, vm, cpu, memory,
374+
disk_size)
375+
376+
# Configure guest customization for the vm
377+
self._configure_vm_guest_cust(vm_instantiation_param, password,
378+
cust_script, hostname)
379+
380+
# Craft the <SourcedItem> element for the vm
381+
sourced_item = E.SourcedItem(
382+
E.Source(href=vm.get('href'),
383+
id=vm.get('id'),
384+
name=vm.get('name'),
385+
type=vm.get('type')))
386+
387+
vm_general_params = E.VmGeneralParams()
388+
if vm_name is not None:
389+
vm_general_params.append(E.Name(vm_name))
390+
391+
# TODO(check if it needs customization if network, cpu or memory..)
392+
if disk_size is None \
393+
and password is None \
394+
and cust_script is None \
395+
and hostname is None:
396+
needs_customization = 'false'
397+
else:
398+
needs_customization = 'true'
399+
vm_general_params.append(E.NeedsCustomization(needs_customization))
400+
sourced_item.append(vm_general_params)
401+
sourced_item.append(vm_instantiation_param)
402+
403+
if storage_profile is not None:
404+
sp = self.get_storage_profile(storage_profile)
405+
vapp_storage_profile = E.StorageProfile(
406+
href=sp.get('href'),
407+
id=sp.get('href').split('/')[-1],
408+
type=sp.get('type'),
409+
name=sp.get('name'))
410+
sourced_item.append(vapp_storage_profile)
411+
412+
vapp_template_params.append(sourced_item)
413+
414+
vapp_template_params.append(E.AllEULAsAccepted(all_eulas_accepted))
415+
non_admin_resource = self.resource
416+
if self.is_admin:
417+
alternate_href = find_link(self.resource,
418+
rel=RelationType.ALTERNATE,
419+
media_type=EntityType.VDC.value).href
420+
non_admin_resource = self.client.get_resource(
421+
alternate_href)
422+
423+
return self.client.post_linked_resource(
424+
non_admin_resource, RelationType.ADD,
425+
EntityType.INSTANTIATE_VAPP_TEMPLATE_PARAMS.value,
426+
vapp_template_params)
427+
428+
def _configure_vapp_network(self, vapp_instantiation_param, network,
429+
network_href, fence_mode):
430+
# Configure the network of the vApp
431+
config = E.Configuration(E.ParentNetwork(href=network_href),
432+
E.FenceMode(fence_mode))
433+
if fence_mode == 'natRouted':
434+
# TODO(need to find the vm_id)
435+
vm_id = None
436+
config.append(
437+
E.Features(
438+
E.NatService(
439+
E.IsEnabled('true'), E.NatType('ipTranslation'),
440+
E.Policy('allowTraffic'),
441+
E.NatRule(
442+
E.OneToOneVmRule(E.MappingMode('automatic'),
443+
E.VAppScopedVmId(vm_id),
444+
E.VmNicId(0))))))
445+
network_config = E.NetworkConfig(config, networkName=network)
446+
network_config_section = E.NetworkConfigSection(
447+
E_OVF.Info('Configuration for logical networks'), network_config)
448+
vapp_instantiation_param.append(network_config_section)
449+
450+
def _configure_vm_netowrk(self, vm_instantiation_param, vm, network,
451+
ip_address, ip_allocation_mode,
452+
network_adapter_type):
453+
if network is None:
454+
return
455+
456+
primary_index = int(
457+
vm.NetworkConnectionSection.PrimaryNetworkConnectionIndex.text)
458+
network_connection = E.NetworkConnection(
459+
E.NetworkConnectionIndex(primary_index), network=network)
460+
461+
if ip_address is not None:
462+
network_connection.append(E.IpAddress(ip_address))
463+
network_connection.append(E.IsConnected('true'))
364464

365465
if ip_allocation_mode == 'static':
366466
ip_allocation_mode = 'manual'
467+
network_connection.append(
468+
E.IpAddressAllocationMode(ip_allocation_mode.upper()))
367469

368-
# Configure network of the first vm
369-
if network_name is not None:
370-
primary_index = int(vms[0].NetworkConnectionSection.
371-
PrimaryNetworkConnectionIndex.text)
372-
network_connection_param = E.NetworkConnection(
373-
E.NetworkConnectionIndex(primary_index), network=network_name)
374-
if ip_address is not None:
375-
network_connection_param.append(E.IpAddress(ip_address))
376-
network_connection_param.append(E.IsConnected('true'))
377-
network_connection_param.append(
378-
E.IpAddressAllocationMode(ip_allocation_mode.upper()))
379-
if network_adapter_type is not None:
380-
network_connection_param.append(
381-
E.NetworkAdapterType(network_adapter_type))
382-
vm_instantiation_param.append(
383-
E.NetworkConnectionSection(
384-
E_OVF.Info(
385-
'Specifies the available VM network connections'),
386-
network_connection_param))
387-
388-
# Configure cpu, memory, disk of the first vm
470+
if network_adapter_type is not None:
471+
network_connection.append(
472+
E.NetworkAdapterType(network_adapter_type))
473+
474+
network_connection_section = E.NetworkConnectionSection(
475+
E_OVF.Info('Specifies the available VM network connections'))
476+
network_connection_section.append(network_connection)
477+
vm_instantiation_param.append(network_connection_section)
478+
479+
def _configure_vm_compute(self, vm_instantiation_param, vm, cpu, memory,
480+
disk_size):
481+
# Configure cpu, memory, disk of the vm
389482
cpu_params = memory_params = disk_params = None
390483
if memory is not None or cpu is not None or disk_size is not None:
391484
virtual_hardware_section = E_OVF.VirtualHardwareSection(
392485
E_OVF.Info('Virtual hardware requirements'))
393-
items = vms[0].xpath(
394-
'//ovf:VirtualHardwareSection/ovf:Item',
395-
namespaces={'ovf': NSMAP['ovf']})
486+
items = vm.xpath('//ovf:VirtualHardwareSection/ovf:Item',
487+
namespaces={'ovf': NSMAP['ovf']})
396488
for item in items:
397489
if memory is not None and memory_params is None:
398490
if item['{' + NSMAP['rasd'] + '}ResourceType'] == 4:
@@ -422,9 +514,10 @@ def instantiate_vapp(self,
422514
virtual_hardware_section.append(disk_params)
423515
vm_instantiation_param.append(virtual_hardware_section)
424516

425-
# Configure guest customization for the vm
426-
if password is not None or cust_script is not None or \
427-
hostname is not None:
517+
def _configure_vm_guest_cust(self, vm_instantiation_param, password,
518+
cust_script, hostname):
519+
if password is not None or cust_script is not None \
520+
or hostname is not None:
428521
guest_customization_param = E.GuestCustomizationSection(
429522
E_OVF.Info('Specifies Guest OS Customization Settings'),
430523
E.Enabled('true'),
@@ -446,72 +539,6 @@ def instantiate_vapp(self,
446539
guest_customization_param.append(E.ComputerName(hostname))
447540
vm_instantiation_param.append(guest_customization_param)
448541

449-
# Craft the <SourcedItem> element for the first vm
450-
sourced_item = E.SourcedItem(
451-
E.Source(
452-
href=vms[0].get('href'),
453-
id=vms[0].get('id'),
454-
name=vms[0].get('name'),
455-
type=vms[0].get('type')))
456-
457-
vm_general_params = E.VmGeneralParams()
458-
if vm_name is not None:
459-
vm_general_params.append(E.Name(vm_name))
460-
461-
# TODO(check if it needs customization if network, cpu or memory...)
462-
if disk_size is None and \
463-
password is None and \
464-
cust_script is None and \
465-
hostname is None:
466-
needs_customization = 'false'
467-
else:
468-
needs_customization = 'true'
469-
vm_general_params.append(E.NeedsCustomization(needs_customization))
470-
sourced_item.append(vm_general_params)
471-
sourced_item.append(vm_instantiation_param)
472-
473-
if storage_profile is not None:
474-
sp = self.get_storage_profile(storage_profile)
475-
vapp_storage_profile = E.StorageProfile(
476-
href=sp.get('href'),
477-
id=sp.get('href').split('/')[-1],
478-
type=sp.get('type'),
479-
name=sp.get('name'))
480-
sourced_item.append(vapp_storage_profile)
481-
482-
# Cook the entire vApp Template instantiation element
483-
deploy_param = 'true' if deploy else 'false'
484-
power_on_param = 'true' if power_on else 'false'
485-
all_eulas_accepted = 'true' if accept_all_eulas else 'false'
486-
487-
vapp_template_params = E.InstantiateVAppTemplateParams(
488-
name=name, deploy=deploy_param, powerOn=power_on_param)
489-
490-
if description is not None:
491-
vapp_template_params.append(E.Description(description))
492-
493-
if vapp_instantiation_param is not None:
494-
vapp_template_params.append(vapp_instantiation_param)
495-
496-
vapp_template_params.append(
497-
E.Source(href=catalog_item.Entity.get('href')))
498-
499-
vapp_template_params.append(sourced_item)
500-
501-
vapp_template_params.append(E.AllEULAsAccepted(all_eulas_accepted))
502-
non_admin_resource = self.resource
503-
if self.is_admin:
504-
alternate_href = find_link(self.resource,
505-
rel=RelationType.ALTERNATE,
506-
media_type=EntityType.VDC.value).href
507-
non_admin_resource = self.client.get_resource(
508-
alternate_href)
509-
510-
return self.client.post_linked_resource(
511-
non_admin_resource, RelationType.ADD,
512-
EntityType.INSTANTIATE_VAPP_TEMPLATE_PARAMS.value,
513-
vapp_template_params)
514-
515542
def list_resources(self, entity_type=None):
516543
"""Fetch information about all resources in the current org vdc.
517544

0 commit comments

Comments
 (0)