VMware: New module for deploy VM from content library template (#60203)
* Fixed format issues reported by pre-check tests * Changes as per review comments. using single api_client between util class and module * re-running the task to check the idempotency of module object
This commit is contained in:
parent
32b0a72547
commit
b75fc87a86
4 changed files with 418 additions and 0 deletions
|
@ -30,6 +30,13 @@ try:
|
||||||
from com.vmware.vapi.std_client import DynamicID
|
from com.vmware.vapi.std_client import DynamicID
|
||||||
from vmware.vapi.vsphere.client import create_vsphere_client
|
from vmware.vapi.vsphere.client import create_vsphere_client
|
||||||
from com.vmware.vapi.std.errors_client import Unauthorized
|
from com.vmware.vapi.std.errors_client import Unauthorized
|
||||||
|
from com.vmware.content.library_client import Item
|
||||||
|
from com.vmware.vcenter_client import (Folder,
|
||||||
|
Datacenter,
|
||||||
|
ResourcePool,
|
||||||
|
Datastore,
|
||||||
|
Cluster,
|
||||||
|
Host)
|
||||||
HAS_VSPHERE = True
|
HAS_VSPHERE = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
VSPHERE_IMP_ERR = traceback.format_exc()
|
VSPHERE_IMP_ERR = traceback.format_exc()
|
||||||
|
@ -243,6 +250,106 @@ class VmwareRestClient(object):
|
||||||
|
|
||||||
return tags
|
return tags
|
||||||
|
|
||||||
|
def get_library_item_by_name(self, name):
|
||||||
|
"""
|
||||||
|
Returns the identifier of the library item with the given name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The name of item to look for
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The item ID or None if the item is not found
|
||||||
|
"""
|
||||||
|
find_spec = Item.FindSpec(name=name)
|
||||||
|
item_ids = self.api_client.content.library.Item.find(find_spec)
|
||||||
|
item_id = item_ids[0] if item_ids else None
|
||||||
|
return item_id
|
||||||
|
|
||||||
|
def get_datacenter_by_name(self, datacenter_name):
|
||||||
|
"""
|
||||||
|
Returns the identifier of a datacenter
|
||||||
|
Note: The method assumes only one datacenter with the mentioned name.
|
||||||
|
"""
|
||||||
|
filter_spec = Datacenter.FilterSpec(names=set([datacenter_name]))
|
||||||
|
datacenter_summaries = self.api_client.vcenter.Datacenter.list(filter_spec)
|
||||||
|
datacenter = datacenter_summaries[0].datacenter if len(datacenter_summaries) > 0 else None
|
||||||
|
return datacenter
|
||||||
|
|
||||||
|
def get_folder_by_name(self, datacenter_name, folder_name):
|
||||||
|
"""
|
||||||
|
Returns the identifier of a folder
|
||||||
|
with the mentioned names.
|
||||||
|
"""
|
||||||
|
datacenter = self.get_datacenter_by_name(datacenter_name)
|
||||||
|
if not datacenter:
|
||||||
|
return None
|
||||||
|
filter_spec = Folder.FilterSpec(type=Folder.Type.VIRTUAL_MACHINE,
|
||||||
|
names=set([folder_name]),
|
||||||
|
datacenters=set([datacenter]))
|
||||||
|
folder_summaries = self.api_client.vcenter.Folder.list(filter_spec)
|
||||||
|
folder = folder_summaries[0].folder if len(folder_summaries) > 0 else None
|
||||||
|
return folder
|
||||||
|
|
||||||
|
def get_resource_pool_by_name(self, datacenter_name, resourcepool_name):
|
||||||
|
"""
|
||||||
|
Returns the identifier of a resource pool
|
||||||
|
with the mentioned names.
|
||||||
|
"""
|
||||||
|
datacenter = self.get_datacenter_by_name(datacenter_name)
|
||||||
|
if not datacenter:
|
||||||
|
return None
|
||||||
|
names = set([resourcepool_name]) if resourcepool_name else None
|
||||||
|
filter_spec = ResourcePool.FilterSpec(datacenters=set([datacenter]),
|
||||||
|
names=names)
|
||||||
|
resource_pool_summaries = self.api_client.vcenter.ResourcePool.list(filter_spec)
|
||||||
|
resource_pool = resource_pool_summaries[0].resource_pool if len(resource_pool_summaries) > 0 else None
|
||||||
|
return resource_pool
|
||||||
|
|
||||||
|
def get_datastore_by_name(self, datacenter_name, datastore_name):
|
||||||
|
"""
|
||||||
|
Returns the identifier of a datastore
|
||||||
|
with the mentioned names.
|
||||||
|
"""
|
||||||
|
datacenter = self.get_datacenter_by_name(datacenter_name)
|
||||||
|
if not datacenter:
|
||||||
|
return None
|
||||||
|
names = set([datastore_name]) if datastore_name else None
|
||||||
|
filter_spec = Datastore.FilterSpec(datacenters=set([datacenter]),
|
||||||
|
names=names)
|
||||||
|
datastore_summaries = self.api_client.vcenter.Datastore.list(filter_spec)
|
||||||
|
datastore = datastore_summaries[0].datastore if len(datastore_summaries) > 0 else None
|
||||||
|
return datastore
|
||||||
|
|
||||||
|
def get_cluster_by_name(self, datacenter_name, cluster_name):
|
||||||
|
"""
|
||||||
|
Returns the identifier of a cluster
|
||||||
|
with the mentioned names.
|
||||||
|
"""
|
||||||
|
datacenter = self.get_datacenter_by_name(datacenter_name)
|
||||||
|
if not datacenter:
|
||||||
|
return None
|
||||||
|
names = set([cluster_name]) if cluster_name else None
|
||||||
|
filter_spec = Cluster.FilterSpec(datacenters=set([datacenter]),
|
||||||
|
names=names)
|
||||||
|
cluster_summaries = self.api_client.vcenter.Cluster.list(filter_spec)
|
||||||
|
cluster = cluster_summaries[0].cluster if len(cluster_summaries) > 0 else None
|
||||||
|
return cluster
|
||||||
|
|
||||||
|
def get_host_by_name(self, datacenter_name, host_name):
|
||||||
|
"""
|
||||||
|
Returns the identifier of a Host
|
||||||
|
with the mentioned names.
|
||||||
|
"""
|
||||||
|
datacenter = self.get_datacenter_by_name(datacenter_name)
|
||||||
|
if not datacenter:
|
||||||
|
return None
|
||||||
|
names = set([host_name]) if host_name else None
|
||||||
|
filter_spec = Host.FilterSpec(datacenters=set([datacenter]),
|
||||||
|
names=names)
|
||||||
|
host_summaries = self.api_client.vcenter.Host.list(filter_spec)
|
||||||
|
host = host_summaries[0].host if len(host_summaries) > 0 else None
|
||||||
|
return host
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def search_svc_object_by_name(service, svc_obj_name=None):
|
def search_svc_object_by_name(service, svc_obj_name=None):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -0,0 +1,271 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright: (c) 2019, Ansible Project
|
||||||
|
# Copyright: (c) 2019, Pavan Bidkar <pbidkar@vmware.com>
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
ANSIBLE_METADATA = {
|
||||||
|
'metadata_version': '1.1',
|
||||||
|
'status': ['preview'],
|
||||||
|
'supported_by': 'community'
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
---
|
||||||
|
module: vmware_content_deploy_template
|
||||||
|
short_description: Deploy Virtual Machine from template stored in content library.
|
||||||
|
description:
|
||||||
|
- Module to deploy virtual machine from template in content library.
|
||||||
|
- Content Library feature is introduced in vSphere 6.0 version, so this module is not supported in the earlier versions of vSphere.
|
||||||
|
- All variables and VMware object names are case sensitive.
|
||||||
|
version_added: '2.9'
|
||||||
|
author:
|
||||||
|
- Pavan Bidkar (@pgbidkar)
|
||||||
|
notes:
|
||||||
|
- Tested on vSphere 6.5, 6.7
|
||||||
|
requirements:
|
||||||
|
- python >= 2.6
|
||||||
|
- PyVmomi
|
||||||
|
- vSphere Automation SDK
|
||||||
|
options:
|
||||||
|
template:
|
||||||
|
description:
|
||||||
|
- The name of template from which VM to be deployed.
|
||||||
|
type: str
|
||||||
|
required: True
|
||||||
|
aliases: ['template_src']
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- The name of the VM to be deployed.
|
||||||
|
type: str
|
||||||
|
required: True
|
||||||
|
aliases: ['vm_name']
|
||||||
|
datacenter:
|
||||||
|
description:
|
||||||
|
- Name of the datacenter, where VM to be deployed.
|
||||||
|
type: str
|
||||||
|
required: True
|
||||||
|
datastore:
|
||||||
|
description:
|
||||||
|
- Name of the datastore to store deployed VM and disk.
|
||||||
|
type: str
|
||||||
|
required: True
|
||||||
|
folder:
|
||||||
|
description:
|
||||||
|
- Name of the folder in datacenter in which to place deployed VM.
|
||||||
|
type: str
|
||||||
|
required: True
|
||||||
|
host:
|
||||||
|
description:
|
||||||
|
- Name of the ESX Host in datacenter in which to place deployed VM.
|
||||||
|
type: str
|
||||||
|
required: True
|
||||||
|
resource_pool:
|
||||||
|
description:
|
||||||
|
- Name of the resourcepool in datacenter in which to place deployed VM.
|
||||||
|
type: str
|
||||||
|
required: False
|
||||||
|
cluster:
|
||||||
|
description:
|
||||||
|
- Name of the cluster in datacenter in which to place deployed VM.
|
||||||
|
type: str
|
||||||
|
required: False
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- The state of Virutal Machine deployed from template in content library.
|
||||||
|
- If set to C(present) and VM does not exists, then VM is created.
|
||||||
|
- If set to C(present) and VM exists, no action is taken.
|
||||||
|
- If set to C(poweredon) and VM does not exists, then VM is created with powered on state.
|
||||||
|
- If set to C(poweredon) and VM exists, no action is taken.
|
||||||
|
type: str
|
||||||
|
required: False
|
||||||
|
default: 'present'
|
||||||
|
choices: [ 'present', 'poweredon' ]
|
||||||
|
extends_documentation_fragment: vmware_rest_client.documentation
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = r'''
|
||||||
|
- name: Deploy Virtual Machine from template in content library
|
||||||
|
vmware_content_deploy_template:
|
||||||
|
hostname: '{{ vcenter_hostname }}'
|
||||||
|
username: '{{ vcenter_username }}'
|
||||||
|
password: '{{ vcenter_password }}'
|
||||||
|
template: rhel_test_template
|
||||||
|
datastore: Shared_NFS_Volume
|
||||||
|
folder: vm
|
||||||
|
datacenter: Sample_DC_1
|
||||||
|
name: Sample_VM
|
||||||
|
resource_pool: test_rp
|
||||||
|
validate_certs: False
|
||||||
|
state: present
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: Deploy Virtual Machine from template in content library with PowerON State
|
||||||
|
vmware_content_deploy_template:
|
||||||
|
hostname: '{{ vcenter_hostname }}'
|
||||||
|
username: '{{ vcenter_username }}'
|
||||||
|
password: '{{ vcenter_password }}'
|
||||||
|
template: rhel_test_template
|
||||||
|
datastore: Shared_NFS_Volume
|
||||||
|
folder: vm
|
||||||
|
datacenter: Sample_DC_1
|
||||||
|
name: Sample_VM
|
||||||
|
resource_pool: test_rp
|
||||||
|
validate_certs: False
|
||||||
|
state: poweredon
|
||||||
|
delegate_to: localhost
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = r'''
|
||||||
|
vm_deploy_info:
|
||||||
|
description: Virtual machine deployment message and vm_id
|
||||||
|
returned: on success
|
||||||
|
type: dict
|
||||||
|
sample: {
|
||||||
|
"msg": "Deployed Virtual Machine 'Sample_VM'.",
|
||||||
|
"vm_id": "vm-1009"
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.vmware_rest_client import VmwareRestClient
|
||||||
|
from ansible.module_utils.vmware import PyVmomi
|
||||||
|
from ansible.module_utils._text import to_native
|
||||||
|
|
||||||
|
HAS_VAUTOMATION_PYTHON_SDK = False
|
||||||
|
try:
|
||||||
|
from com.vmware.vcenter.vm_template_client import LibraryItems
|
||||||
|
HAS_VAUTOMATION_PYTHON_SDK = True
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class VmwareContentDeployTemplate(VmwareRestClient):
|
||||||
|
def __init__(self, module):
|
||||||
|
"""Constructor."""
|
||||||
|
super(VmwareContentDeployTemplate, self).__init__(module)
|
||||||
|
self.template_service = self.api_client.vcenter.vm_template.LibraryItems
|
||||||
|
self.template_name = self.params.get('template')
|
||||||
|
self.vm_name = self.params.get('name')
|
||||||
|
self.datacenter = self.params.get('datacenter')
|
||||||
|
self.datastore = self.params.get('datastore')
|
||||||
|
self.folder = self.params.get('folder')
|
||||||
|
self.resourcepool = self.params.get('resource_pool')
|
||||||
|
self.cluster = self.params.get('cluster')
|
||||||
|
self.host = self.params.get('host')
|
||||||
|
|
||||||
|
def deploy_vm_from_template(self, power_on=False):
|
||||||
|
# Find the datacenter by the given datacenter name
|
||||||
|
self.datacenter_id = self.get_datacenter_by_name(datacenter_name=self.datacenter)
|
||||||
|
if not self.datacenter_id:
|
||||||
|
self.module.fail_json(msg="Failed to find the datacenter %s" % self.datacenter)
|
||||||
|
# Find the datastore by the given datastore name
|
||||||
|
self.datastore_id = self.get_datastore_by_name(self.datacenter, self.datastore)
|
||||||
|
if not self.datastore_id:
|
||||||
|
self.module.fail_json(msg="Failed to find the datastore %s" % self.datastore)
|
||||||
|
# Find the LibraryItem (Template) by the given LibraryItem name
|
||||||
|
self.library_item_id = self.get_library_item_by_name(self.template_name)
|
||||||
|
if not self.library_item_id:
|
||||||
|
self.module.fail_json(msg="Failed to find the library Item %s" % self.template_name)
|
||||||
|
# Find the folder by the given folder name
|
||||||
|
self.folder_id = self.get_folder_by_name(self.datacenter, self.folder)
|
||||||
|
if not self.folder_id:
|
||||||
|
self.module.fail_json(msg="Failed to find the folder %s" % self.folder)
|
||||||
|
# Find the Host by given HostName
|
||||||
|
self.host_id = self.get_host_by_name(self.datacenter, self.host)
|
||||||
|
if not self.host_id:
|
||||||
|
self.module.fail_json(msg="Failed to find the Host %s" % self.host)
|
||||||
|
# Find the resourcepool by the given resourcepool name
|
||||||
|
self.resourcepool_id = None
|
||||||
|
if self.resourcepool:
|
||||||
|
self.resourcepool_id = self.get_resource_pool_by_name(self.datacenter, self.resourcepool)
|
||||||
|
if not self.resourcepool_id:
|
||||||
|
self.module.fail_json(msg="Failed to find the resource_pool %s" % self.resourcepool)
|
||||||
|
# Find the Cluster by the given Cluster name
|
||||||
|
self.cluster_id = None
|
||||||
|
if self.cluster:
|
||||||
|
self.cluster_id = self.get_resource_pool_by_name(self.datacenter, self.resourcepool)
|
||||||
|
if not self.cluster_id:
|
||||||
|
self.module.fail_json(msg="Failed to find the Cluster %s" % self.cluster)
|
||||||
|
# Create VM placement specs
|
||||||
|
self.placement_spec = LibraryItems.DeployPlacementSpec(folder=self.folder_id,
|
||||||
|
host=self.host_id
|
||||||
|
)
|
||||||
|
if self.resourcepool_id or self.cluster_id:
|
||||||
|
self.placement_spec.resource_pool = self.resourcepool_id
|
||||||
|
self.placement_spec.cluster = self.cluster_id
|
||||||
|
self.vm_home_storage_spec = LibraryItems.DeploySpecVmHomeStorage(datastore=to_native(self.datastore_id))
|
||||||
|
self.disk_storage_spec = LibraryItems.DeploySpecDiskStorage(datastore=to_native(self.datastore_id))
|
||||||
|
self.deploy_spec = LibraryItems.DeploySpec(name=self.vm_name,
|
||||||
|
placement=self.placement_spec,
|
||||||
|
vm_home_storage=self.vm_home_storage_spec,
|
||||||
|
disk_storage=self.disk_storage_spec,
|
||||||
|
powered_on=power_on
|
||||||
|
)
|
||||||
|
vm_id = self.template_service.deploy(self.library_item_id, self.deploy_spec)
|
||||||
|
if vm_id:
|
||||||
|
self.module.exit_json(
|
||||||
|
changed=True,
|
||||||
|
vm_deploy_info=dict(
|
||||||
|
msg="Deployed Virtual Machine '%s'." % self.vm_name,
|
||||||
|
vm_id=vm_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.module.exit_json(changed=False,
|
||||||
|
vm_deploy_info=dict(msg="Virtual Machine deployment failed", vm_id=''))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argument_spec = VmwareRestClient.vmware_client_argument_spec()
|
||||||
|
argument_spec.update(
|
||||||
|
state=dict(type='str', default='present',
|
||||||
|
choices=['present', 'poweredon']),
|
||||||
|
template=dict(type='str', aliases=['template_src'], required=True),
|
||||||
|
name=dict(type='str', required=True, aliases=['vm_name']),
|
||||||
|
datacenter=dict(type='str', required=True),
|
||||||
|
datastore=dict(type='str', required=True),
|
||||||
|
folder=dict(type='str', required=True),
|
||||||
|
host=dict(type='str', required=True),
|
||||||
|
resource_pool=dict(type='str', required=False),
|
||||||
|
cluster=dict(type='str', required=False),
|
||||||
|
)
|
||||||
|
module = AnsibleModule(argument_spec=argument_spec,
|
||||||
|
supports_check_mode=True)
|
||||||
|
result = {'failed': False, 'changed': False}
|
||||||
|
pyv = PyVmomi(module=module)
|
||||||
|
vm = pyv.get_vm()
|
||||||
|
if vm:
|
||||||
|
module.exit_json(
|
||||||
|
changed=False,
|
||||||
|
vm_deploy_info=dict(
|
||||||
|
msg="Virtual Machine '%s' already Exists." % module.params['name'],
|
||||||
|
vm_id=vm._moId,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
vmware_contentlib_create = VmwareContentDeployTemplate(module)
|
||||||
|
if module.params['state'] in ['present']:
|
||||||
|
if module.check_mode:
|
||||||
|
result.update(
|
||||||
|
vm_name=module.params['name'],
|
||||||
|
changed=True,
|
||||||
|
desired_operation='Create VM with PowerOff State',
|
||||||
|
)
|
||||||
|
module.exit_json(**result)
|
||||||
|
vmware_contentlib_create.deploy_vm_from_template()
|
||||||
|
if module.params['state'] == 'poweredon':
|
||||||
|
if module.check_mode:
|
||||||
|
result.update(
|
||||||
|
vm_name=module.params['name'],
|
||||||
|
changed=True,
|
||||||
|
desired_operation='Create VM with PowerON State',
|
||||||
|
)
|
||||||
|
module.exit_json(**result)
|
||||||
|
vmware_contentlib_create.deploy_vm_from_template(power_on=True)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,3 @@
|
||||||
|
cloud/vcenter
|
||||||
|
unsupported
|
||||||
|
needs/target/prepare_vmware_tests
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Test code for the deploy VM from content library template.
|
||||||
|
# Copyright: (c) 2019, Pavan Bidkar <pbidkar@vmware.com>
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
- import_role:
|
||||||
|
name: prepare_vmware_tests
|
||||||
|
vars:
|
||||||
|
setup_datacenter: true
|
||||||
|
|
||||||
|
- when: vcsim is not defined
|
||||||
|
block:
|
||||||
|
- &deploy_vm_from_content_library_template
|
||||||
|
vmware_content_deploy_template:
|
||||||
|
hostname: '{{ vcenter_hostname }}'
|
||||||
|
username: '{{ vcenter_username }}'
|
||||||
|
password: '{{ vcenter_password }}'
|
||||||
|
template: '{{ test_vm_temp }}'
|
||||||
|
datastore: '{{ ds1 }}'
|
||||||
|
datacenter: '{{ dc1 }}'
|
||||||
|
folder: '{{ f0 }}'
|
||||||
|
host: '{{ esx1 }}'
|
||||||
|
name: 'test_content_deploy_vm'
|
||||||
|
state: poweredon
|
||||||
|
validate_certs: false
|
||||||
|
register: template_deploy
|
||||||
|
|
||||||
|
- name: Check VM deployed successfully
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- template_deploy.changed
|
||||||
|
|
||||||
|
- <<: *deploy_vm_from_content_library_template
|
||||||
|
name: Deploy VM from template again
|
||||||
|
|
||||||
|
- name: Check VM with same name is deployed
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not template_deploy.changed
|
Loading…
Reference in a new issue