diff --git a/cloud/vmware/vca_vapp.py b/cloud/vmware/vca_vapp.py new file mode 100644 index 00000000000..1b3aeff93c0 --- /dev/null +++ b/cloud/vmware/vca_vapp.py @@ -0,0 +1,747 @@ +#!/usr/bin/python + +# Copyright (c) 2015 VMware, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +DOCUMENTATION = ''' +--- +module: vca_vapp +short_description: create, terminate, start or stop a vm in vca +description: + - Creates or terminates vca vms. +version_added: "2.0" +options: + username: + version_added: "2.0" + description: + - The vca username or email address, if not set the environment variable VCA_USER is checked for the username. + required: false + default: None + password: + version_added: "2.0" + description: + - The vca password, if not set the environment variable VCA_PASS is checked for the password + required: false + default: None + org: + version_added: "2.0" + description: + - The org to login to for creating vapp, mostly set when the service_type is vdc. + required: false + default: None + service_id: + version_added: "2.0" + description: + - The service id in a vchs environment to be used for creating the vapp + required: false + default: None + host: + version_added: "2.0" + description: + - The authentication host to be used when service type is vcd. + required: false + default: None + api_version: + version_added: "2.0" + description: + - The api version to be used with the vca + required: false + default: "5.7" + service_type: + version_added: "2.0" + description: + - The type of service we are authenticating against + required: false + default: vca + choices: [ "vca", "vchs", "vcd" ] + state: + version_added: "2.0" + description: + - if the object should be added or removed + required: false + default: present + choices: [ "present", "absent" ] + catalog_name: + version_added: "2.0" + description: + - The catalog from which the vm template is used. + required: false + default: "Public Catalog" + script: + version_added: "2.0" + description: + - The path to script that gets injected to vm during creation. + required: false + default: "Public Catalog" + template_name: + version_added: "2.0" + description: + - The template name from which the vm should be created. + required: True + network_name: + version_added: "2.0" + description: + - The network name to which the vm should be attached. + required: false + default: 'None' + network_ip: + version_added: "2.0" + description: + - The ip address that should be assigned to vm when the ip assignment type is static + required: false + default: None + network_mode: + version_added: "2.0" + description: + - The network mode in which the ip should be allocated. + required: false + default: pool + choices: [ "pool", "dhcp", 'static' ] + instance_id:: + version_added: "2.0" + description: + - The instance id of the region in vca flavour where the vm should be created + required: false + default: None + wait: + version_added: "2.0" + description: + - If the module should wait if the operation is poweroff or poweron, is better to wait to report the right state. + required: false + default: True + wait_timeout: + version_added: "2.0" + description: + - The wait timeout when wait is set to true + required: false + default: 250 + vdc_name: + version_added: "2.0" + description: + - The name of the vdc where the vm should be created. + required: false + default: None + vm_name: + version_added: "2.0" + description: + - The name of the vm to be created, the vapp is named the same as the vapp name + required: false + default: 'default_ansible_vm1' + vm_cpus: + version_added: "2.0" + description: + - The number if cpus to be added to the vm + required: false + default: None + vm_memory: + version_added: "2.0" + description: + - The amount of memory to be added to vm in megabytes + required: false + default: None + verify_certs: + version_added: "2.0" + description: + - If the certificates of the authentication is to be verified + required: false + default: True + admin_password: + version_added: "2.0" + description: + - The password to be set for admin + required: false + default: None + operation: + version_added: "2.0" + description: + - The operation to be done on the vm + required: false + default: poweroff + choices: [ 'shutdown', 'poweroff', 'poweron', 'reboot', 'reset', 'suspend' ] + +''' + +EXAMPLES = ''' + +#Create a vm in an vca environment. The username password is not set as they are set in environment + +- hosts: localhost + connection: local + tasks: + - vca_vapp: + operation: poweroff + instance_id: 'b15ff1e5-1024-4f55-889f-ea0209726282' + vdc_name: 'benz_ansible' + vm_name: benz + vm_cpus: 2 + vm_memory: 1024 + network_mode: pool + template_name: "CentOS63-32BIT" + admin_password: "Password!123" + network_name: "default-routed-network" + +#Create a vm in a vchs environment. + +- hosts: localhost + connection: local + tasks: + - vca_app: + operation: poweron + service_id: '9-69' + vdc_name: 'Marketing' + service_type: 'vchs' + vm_name: benz + vm_cpus: 1 + script: "/tmp/configure_vm.sh" + catalog_name: "Marketing-Catalog" + template_name: "Marketing-Ubuntu-1204x64" + vm_memory: 512 + network_name: "M49-default-isolated" + +#create a vm in a vdc environment + +- hosts: localhost + connection: local + tasks: + - vca_vapp: + operation: poweron + org: IT20 + host: "mycloud.vmware.net" + api_version: "5.5" + service_type: vcd + vdc_name: 'IT20 Data Center (Beta)' + vm_name: benz + vm_cpus: 1 + catalog_name: "OS Templates" + template_name: "CentOS 6.5 64Bit CLI" + network_mode: pool + + +''' + + +import time, json, xmltodict + +HAS_PYVCLOUD = False +try: + from pyvcloud.vcloudair import VCA + HAS_PYVCLOUD = True +except ImportError: + pass + +SERVICE_MAP = {'vca': 'ondemand', 'vchs': 'subscription', 'vcd': 'vcd'} +LOGIN_HOST = {} +LOGIN_HOST['vca'] = 'vca.vmware.com' +LOGIN_HOST['vchs'] = 'vchs.vmware.com' +VM_COMPARE_KEYS = ['admin_password', 'status', 'cpus', 'memory_mb'] + +def vm_state(val=None): + if val == 8: + return "Power_Off" + elif val == 4: + return "Power_On" + else: + return "Unknown Status" + +def serialize_instances(instance_list): + instances = [] + for i in instance_list: + instances.append(dict(apiUrl=i['apiUrl'], instance_id=i['id'])) + return instances + +def get_catalogs(vca): + catalogs = vca.get_catalogs() + results = [] + for catalog in catalogs: + if catalog.CatalogItems and catalog.CatalogItems.CatalogItem: + for item in catalog.CatalogItems.CatalogItem: + results.append([catalog.name, item.name]) + else: + results.append([catalog.name, '']) + return results + +def vca_login(module=None): + service_type = module.params.get('service_type') + username = module.params.get('username') + password = module.params.get('password') + instance = module.params.get('instance_id') + org = module.params.get('org') + service = module.params.get('service_id') + vdc_name = module.params.get('vdc_name') + version = module.params.get('api_version') + verify = module.params.get('verify_certs') + if not vdc_name: + if service_type == 'vchs': + vdc_name = module.params.get('service_id') + if not org: + if service_type == 'vchs': + if vdc_name: + org = vdc_name + else: + org = service + if service_type == 'vcd': + host = module.params.get('host') + else: + host = LOGIN_HOST[service_type] + + if not username: + if 'VCA_USER' in os.environ: + username = os.environ['VCA_USER'] + if not password: + if 'VCA_PASS' in os.environ: + password = os.environ['VCA_PASS'] + if not username or not password: + module.fail_json(msg = "Either the username or password is not set, please check") + + if service_type == 'vchs': + version = '5.6' + if service_type == 'vcd': + if not version: + version == '5.6' + + + vca = VCA(host=host, username=username, service_type=SERVICE_MAP[service_type], version=version, verify=verify) + + if service_type == 'vca': + if not vca.login(password=password): + module.fail_json(msg = "Login Failed: Please check username or password", error=vca.response.content) + if not vca.login_to_instance(password=password, instance=instance, token=None, org_url=None): + s_json = serialize_instances(vca.instances) + module.fail_json(msg = "Login to Instance failed: Seems like instance_id provided is wrong .. Please check",\ + valid_instances=s_json) + if not vca.login_to_instance(instance=instance, password=None, token=vca.vcloud_session.token, + org_url=vca.vcloud_session.org_url): + module.fail_json(msg = "Error logging into org for the instance", error=vca.response.content) + return vca + + if service_type == 'vchs': + if not vca.login(password=password): + module.fail_json(msg = "Login Failed: Please check username or password", error=vca.response.content) + if not vca.login(token=vca.token): + module.fail_json(msg = "Failed to get the token", error=vca.response.content) + if not vca.login_to_org(service, org): + module.fail_json(msg = "Failed to login to org, Please check the orgname", error=vca.response.content) + return vca + + if service_type == 'vcd': + if not vca.login(password=password, org=org): + module.fail_json(msg = "Login Failed: Please check username or password or host parameters") + if not vca.login(password=password, org=org): + module.fail_json(msg = "Failed to get the token", error=vca.response.content) + if not vca.login(token=vca.token, org=org, org_url=vca.vcloud_session.org_url): + module.fail_json(msg = "Failed to login to org", error=vca.response.content) + return vca + +def set_vm_state(module=None, vca=None, state=None): + wait = module.params.get('wait') + wait_tmout = module.params.get('wait_timeout') + vm_name = module.params.get('vm_name') + vdc_name = module.params.get('vdc_name') + vapp_name = module.params.get('vm_name') + service_type = module.params.get('service_type') + service_id = module.params.get('service_id') + if service_type == 'vchs' and not vdc_name: + vdc_name = service_id + vdc = vca.get_vdc(vdc_name) + if wait: + tmout = time.time() + wait_tmout + while tmout > time.time(): + vapp = vca.get_vapp(vdc, vapp_name) + vms = filter(lambda vm: vm['name'] == vm_name, vapp.get_vms_details()) + vm = vms[0] + if vm['status'] == state: + return True + time.sleep(5) + module.fail_json(msg="Timeut waiting for the vms state to change") + return True + +def vm_details(vdc=None, vapp=None, vca=None): + table = [] + networks = [] + vm_name = vapp + vdc1 = vca.get_vdc(vdc) + if not vdc1: + module.fail_json(msg = "Error getting the vdc, Please check the vdc name") + vap = vca.get_vapp(vdc1, vapp) + if vap: + vms = filter(lambda vm: vm['name'] == vm_name, vap.get_vms_details()) + networks = vap.get_vms_network_info() + if len(networks[0]) >= 1: + table.append(dict(vm_info=vms[0], network_info=networks[0][0])) + else: + table.append(dict(vm_info=vms[0], network_info=networks[0])) + return table + + +def vapp_attach_net(module=None, vca=None, vapp=None): + network_name = module.params.get('network_name') + service_type = module.params.get('service_type') + vdc_name = module.params.get('vdc_name') + mode = module.params.get('network_mode') + if mode.upper() == 'STATIC': + network_ip = module.params.get('network_ip') + else: + network_ip = None + if not vdc_name: + if service_type == 'vchs': + vdc_name = module.params.get('service_id') + nets = filter(lambda n: n.name == network_name, vca.get_networks(vdc_name)) + if len(nets) <= 1: + net_task = vapp.disconnect_vms() + if not net_task: + module.fail_json(msg="Failure in detattaching vms from vnetworks", error=vapp.response.content) + if not vca.block_until_completed(net_task): + module.fail_json(msg="Failure in waiting for detaching vms from vnetworks", error=vapp.response.content) + net_task = vapp.disconnect_from_networks() + if not net_task: + module.fail_json(msg="Failure in detattaching network from vapp", error=vapp.response.content) + if not vca.block_until_completed(net_task): + module.fail_json(msg="Failure in waiting for detaching network from vapp", error=vapp.response.content) + if not network_name: + return True + + net_task = vapp.connect_to_network(nets[0].name, nets[0].href) + if not net_task: + module.fail_json(msg="Failure in attaching network to vapp", error=vapp.response.content) + if not vca.block_until_completed(net_task): + module.fail_json(msg="Failure in waiting for attching network to vapp", error=vapp.response.content) + + net_task = vapp.connect_vms(nets[0].name, connection_index=0, ip_allocation_mode=mode.upper(), ip_address=network_ip ) + if not net_task: + module.fail_json(msg="Failure in attaching network to vm", error=vapp.response.content) + if not vca.block_until_completed(net_task): + module.fail_json(msg="Failure in waiting for attaching network to vm", error=vapp.response.content) + return True + nets = [] + for i in vca.get_networks(vdc_name): + nets.append(i.name) + module.fail_json(msg="Seems like network_name is not found in the vdc, please check Available networks as above", Available_networks=nets) + +def create_vm(vca=None, module=None): + vm_name = module.params.get('vm_name') + operation = module.params.get('operation') + vm_cpus = module.params.get('vm_cpus') + vm_memory = module.params.get('vm_memory') + catalog_name = module.params.get('catalog_name') + template_name = module.params.get('template_name') + vdc_name = module.params.get('vdc_name') + network_name = module.params.get('network_name') + service_type = module.params.get('service_type') + admin_pass = module.params.get('admin_password') + script = module.params.get('script') + vapp_name = vm_name + + if not vdc_name: + if service_type == 'vchs': + vdc_name = module.params.get('service_id') + task = vca.create_vapp(vdc_name, vapp_name, template_name, catalog_name, vm_name=None) + if not task: + catalogs = get_catalogs(vca) + module.fail_json(msg="Error in Creating VM, Please check catalog or template, Available catalogs and templates are as above or check the error field", catalogs=catalogs, errors=vca.response.content) + if not vca.block_until_completed(task): + module.fail_json(msg = "Error in waiting for VM Creation, Please check logs", errors=vca.response.content) + vdc = vca.get_vdc(vdc_name) + if not vdc: + module.fail_json(msg = "Error getting the vdc, Please check the vdc name", errors=vca.response.content) + + vapp = vca.get_vapp(vdc, vapp_name) + task = vapp.modify_vm_name(1, vm_name) + if not task: + module.fail_json(msg="Error in setting the vm_name to vapp_name", errors=vca.response.content) + if not vca.block_until_completed(task): + module.fail_json(msg = "Error in waiting for VM Renaming, Please check logs", errors=vca.response.content) + vapp = vca.get_vapp(vdc, vapp_name) + task = vapp.customize_guest_os(vm_name, computer_name=vm_name) + if not task: + module.fail_json(msg="Error in setting the computer_name to vm_name", errors=vca.response.content) + if not vca.block_until_completed(task): + module.fail_json(msg = "Error in waiting for Computer Renaming, Please check logs", errors=vca.response.content) + + + if network_name: + vapp = vca.get_vapp(vdc, vapp_name) + if not vapp_attach_net(module, vca, vapp): + module.fail_json(msg= "Attaching network to VM fails", errors=vca.response.content) + + if vm_cpus: + vapp = vca.get_vapp(vdc, vapp_name) + task = vapp.modify_vm_cpu(vm_name, vm_cpus) + if not task: + module.fail_json(msg="Error adding cpu", error=vapp.resonse.contents) + if not vca.block_until_completed(task): + module.fail_json(msg="Failure in waiting for modifying cpu", error=vapp.response.content) + + if vm_memory: + vapp = vca.get_vapp(vdc, vapp_name) + task = vapp.modify_vm_memory(vm_name, vm_memory) + if not task: + module.fail_json(msg="Error adding memory", error=vapp.resonse.contents) + if not vca.block_until_completed(task): + module.fail_json(msg="Failure in waiting for modifying memory", error=vapp.response.content) + + if admin_pass: + vapp = vca.get_vapp(vdc, vapp_name) + task = vapp.customize_guest_os(vm_name, customization_script=None, + computer_name=None, admin_password=admin_pass, + reset_password_required=False) + if not task: + module.fail_json(msg="Error adding admin password", error=vapp.resonse.contents) + if not vca.block_until_completed(task): + module.fail_json(msg = "Error in waiting for resettng admin pass, Please check logs", errors=vapp.response.content) + + if script: + vapp = vca.get_vapp(vdc, vapp_name) + if os.path.exists(os.path.expanduser(script)): + file_contents = open(script, 'r') + task = vapp.customize_guest_os(vm_name, customization_script=file_contents.read()) + if not task: + module.fail_json(msg="Error adding customization script", error=vapp.resonse.contents) + if not vca.block_until_completed(task): + module.fail_json(msg = "Error in waiting for customization script, please check logs", errors=vapp.response.content) + task = vapp.force_customization(vm_name, power_on=False ) + if not task: + module.fail_json(msg="Error adding customization script", error=vapp.resonse.contents) + if not vca.block_until_completed(task): + module.fail_json(msg = "Error in waiting for customization script, please check logs", errors=vapp.response.content) + else: + module.fail_json(msg = "The file specified in script paramter is not avaialable or accessible") + + vapp = vca.get_vapp(vdc, vapp_name) + if operation == 'poweron': + vapp.poweron() + set_vm_state(module, vca, state='Powered on') + elif operation == 'poweroff': + vapp.poweroff() + elif operation == 'reboot': + vapp.reboot() + elif operation == 'reset': + vapp.reset() + elif operation == 'suspend': + vapp.suspend() + elif operation == 'shutdown': + vapp.shutdown() + details = vm_details(vdc_name, vapp_name, vca) + module.exit_json(changed=True, msg="VM created", vm_details=details[0]) + +def vapp_reconfigure(module=None, diff=None, vm=None, vca=None, vapp=None, vdc_name=None): + flag = False + vapp_name = module.params.get('vm_name') + vm_name = module.params.get('vm_name') + cpus = module.params.get('vm_cpus') + memory = module.params.get('vm_memory') + admin_pass = module.params.get('admin_password') + + if 'status' in diff: + operation = module.params.get('operation') + if operation == 'poweroff': + vapp.poweroff() + set_vm_state(module, vca, state='Powered off') + flag = True + if 'network' in diff: + vapp_attach_net(module, vca, vapp) + flag = True + if 'cpus' in diff: + task = vapp.modify_vm_cpu(vm_name, cpus) + if not vca.block_until_completed(task): + module.fail_json(msg="Failure in waiting for modifying cpu, might be vm is powered on and doesnt support hotplugging", error=vapp.response.content) + flag = True + if 'memory_mb' in diff: + task = vapp.modify_vm_memory(vm_name, memory) + if not vca.block_until_completed(task): + module.fail_json(msg="Failure in waiting for modifying memory, might be vm is powered on and doesnt support hotplugging", error=vapp.response.content) + flag = True + if 'admin_password' in diff: + task = vapp.customize_guest_os(vm_name, customization_script=None, + computer_name=None, admin_password=admin_pass, + reset_password_required=False) + if not task: + module.fail_json(msg="Error adding admin password", error=vapp.resonse.contents) + if not vca.block_until_completed(task): + module.fail_json(msg = "Error in waiting for resettng admin pass, Please check logs", errors=vapp.response.content) + flag = True + if 'status' in diff: + operation = module.params.get('operation') + if operation == 'poweron': + vapp.poweron() + set_vm_state(module, vca, state='Powered on') + elif operation == 'reboot': + vapp.reboot() + elif operation == 'reset': + vapp.reset() + elif operation == 'suspend': + vapp.suspend() + elif operation == 'shutdown': + vapp.shutdown() + flag = True + details = vm_details(vdc_name, vapp_name, vca) + if flag: + module.exit_json(changed=True, msg="VM reconfigured", vm_details=details[0]) + module.exit_json(changed=False, msg="VM exists as per configuration",\ + vm_details=details[0]) + +def vm_exists(module=None, vapp=None, vca=None, vdc_name=None): + vm_name = module.params.get('vm_name') + operation = module.params.get('operation') + vm_cpus = module.params.get('vm_cpus') + vm_memory = module.params.get('vm_memory') + network_name = module.params.get('network_name') + admin_pass = module.params.get('admin_password') + + d_vm = {} + d_vm['name'] = vm_name + d_vm['cpus'] = vm_cpus + d_vm['memory_mb'] = vm_memory + d_vm['admin_password'] = admin_pass + + if operation == 'poweron': + d_vm['status'] = 'Powered on' + elif operation == 'poweroff': + d_vm['status'] = 'Powered off' + else: + d_vm['status'] = 'operate' + + vms = filter(lambda vm: vm['name'] == vm_name, vapp.get_vms_details()) + if len(vms) > 1: + module.fail_json(msg = "The vapp seems to have more than one vm with same name,\ + currently we only support a single vm deployment") + elif len(vms) == 0: + return False + + else: + vm = vms[0] + diff = [] + for i in VM_COMPARE_KEYS: + if not d_vm[i]: + continue + if vm[i] != d_vm[i]: + diff.append(i) + if len(diff) == 1 and 'status' in diff: + vapp_reconfigure(module, diff, vm, vca, vapp, vdc_name) + networks = vapp.get_vms_network_info() + if not network_name and len(networks) >=1: + if len(networks[0]) >= 1: + if networks[0][0]['network_name'] != 'none': + diff.append('network') + if not network_name: + if len(diff) == 0: + return True + if not networks[0] and network_name: + diff.append('network') + if networks[0]: + if len(networks[0]) >= 1: + if networks[0][0]['network_name'] != network_name: + diff.append('network') + if vm['status'] != 'Powered off': + if operation != 'poweroff' and len(diff) > 0: + module.fail_json(msg="To change any properties of a vm, The vm should be in Powered Off state") + if len(diff) == 0: + return True + else: + vapp_reconfigure(module, diff, vm, vca, vapp, vdc_name) + +def main(): + module = AnsibleModule( + argument_spec=dict( + username = dict(default=None), + password = dict(default=None), + org = dict(default=None), + service_id = dict(default=None), + script = dict(default=None), + host = dict(default=None), + api_version = dict(default='5.7'), + service_type = dict(default='vca', choices=['vchs', 'vca', 'vcd']), + state = dict(default='present', choices = ['present', 'absent']), + catalog_name = dict(default="Public Catalog"), + template_name = dict(default=None, required=True), + network_name = dict(default=None), + network_ip = dict(default=None), + network_mode = dict(default='pool', choices=['dhcp', 'static', 'pool']), + instance_id = dict(default=None), + wait = dict(default=True, type='bool'), + wait_timeout = dict(default=250, type='int'), + vdc_name = dict(default=None), + vm_name = dict(default='default_ansible_vm1'), + vm_cpus = dict(default=None, type='int'), + verify_certs = dict(default=True, type='bool'), + vm_memory = dict(default=None, type='int'), + admin_password = dict(default=None), + operation = dict(default='poweroff', choices=['shutdown', 'poweroff', 'poweron', 'reboot', 'reset', 'suspend']) + ) + ) + + + vdc_name = module.params.get('vdc_name') + vm_name = module.params.get('vm_name') + org = module.params.get('org') + service = module.params.get('service_id') + state = module.params.get('state') + service_type = module.params.get('service_type') + host = module.params.get('host') + instance_id = module.params.get('instance_id') + network_mode = module.params.get('network_mode') + network_ip = module.params.get('network_ip') + vapp_name = vm_name + + if not HAS_PYVCLOUD: + module.fail_json(msg="python module pyvcloud is needed for this module") + + if network_mode.upper() == 'STATIC': + if not network_ip: + module.fail_json(msg="if network_mode is STATIC, network_ip is mandatory") + + if service_type == 'vca': + if not instance_id: + module.fail_json(msg="When service type is vca the instance_id parameter is mandatory") + if not vdc_name: + module.fail_json(msg="When service type is vca the vdc_name parameter is mandatory") + + if service_type == 'vchs': + if not service: + module.fail_json(msg="When service type vchs the service_id parameter is mandatory") + if not org: + org = service + if not vdc_name: + vdc_name = service + if service_type == 'vcd': + if not host: + module.fail_json(msg="When service type is vcd host parameter is mandatory") + + vca = vca_login(module) + vdc = vca.get_vdc(vdc_name) + if not vdc: + module.fail_json(msg = "Error getting the vdc, Please check the vdc name") + vapp = vca.get_vapp(vdc, vapp_name) + if vapp: + if state == 'absent': + task = vca.delete_vapp(vdc_name, vapp_name) + if not vca.block_until_completed(task): + module.fail_json(msg="failure in deleting vapp") + module.exit_json(changed=True, msg="Vapp deleted") + if vm_exists(module, vapp, vca, vdc_name ): + details = vm_details(vdc_name, vapp_name, vca) + module.exit_json(changed=False, msg="vapp exists", vm_details=details[0]) + else: + create_vm(vca, module) + if state == 'absent': + module.exit_json(changed=False, msg="Vapp does not exist") + create_vm(vca, module) + + + +# import module snippets +from ansible.module_utils.basic import * +if __name__ == '__main__': + main()