From abb956d9eba40fb5e533720cebd5930520e46ea9 Mon Sep 17 00:00:00 2001 From: goshkis Date: Tue, 20 Feb 2018 22:29:13 -0800 Subject: [PATCH] vApp properties support (#32579) * Adding support for vApp properties. * vm specification updated only if changes have to be applied. I.e. subsequent updates with the same data will not trigger changed state * Auxiliary variables renamed, hope this makes the code more readable * Integration tests changed - re-adding the same properties test not implemented, but tested on real vCenter deployment * fixing documentation "version_added" for the feature * Addressing reviewers comments #2: * documentation updated with the only meaningful value for "option" attribute - "remove" * Fixed improperly handled case when user requested "add" operation for existent property * vApp configuration is updated only with properties that contains changes, not with all properties requested by user --- .../modules/cloud/vmware/vmware_guest.py | 108 ++++++++++++++++++ .../targets/vmware_guest/tasks/main.yml | 1 + .../vmware_guest/tasks/vapp_d1_c1_f0.yml | 107 +++++++++++++++++ 3 files changed, 216 insertions(+) create mode 100644 test/integration/targets/vmware_guest/tasks/vapp_d1_c1_f0.yml diff --git a/lib/ansible/modules/cloud/vmware/vmware_guest.py b/lib/ansible/modules/cloud/vmware/vmware_guest.py index 8e78fc5539..b6bb720451 100644 --- a/lib/ansible/modules/cloud/vmware/vmware_guest.py +++ b/lib/ansible/modules/cloud/vmware/vmware_guest.py @@ -241,6 +241,16 @@ options: - ' - C(runonce) (list): List of commands to run at first user logon.' - ' - C(timezone) (int): Timezone (See U(https://msdn.microsoft.com/en-us/library/ms912391.aspx)).' version_added: '2.3' + vapp_properties: + description: + - A list of vApp properties + - 'For full list of attibutes and types refer to: U(https://github.com/vmware/pyvmomi/blob/master/docs/vim/vApp/PropertyInfo.rst)' + - 'Basic attributes are:' + - ' - C(id) (string): Property id - required' + - ' - C(value) (string): Property value' + - ' - C(type) (string): Value type, string type by default.' + - ' - C(operation): C(remove): This attribute is required only when removing properties' + version_added: '2.6' extends_documentation_fragment: vmware.documentation ''' @@ -359,6 +369,22 @@ EXAMPLES = r''' uuid: 421e4592-c069-924d-ce20-7e7533fab926 state: absent delegate_to: localhost + +- name: Manipulate vApp properties + vmware_guest: + hostname: 192.168.1.209 + username: administrator@vsphere.local + password: vmware + name: vm_name + state: present + vapp_properties: + - id: remoteIP + category: Backup + label: Backup server IP + type: string + value: 10.10.10.1 + - id: old_property + operation: remove ''' RETURN = r''' @@ -1052,6 +1078,86 @@ class PyVmomiHelper(PyVmomi): self.configspec.deviceChange.append(nic) self.change_detected = True + def configure_vapp_properties(self, vm_obj): + if len(self.params['vapp_properties']) == 0: + return + + for x in self.params['vapp_properties']: + if not x.get('id'): + self.module.fail_json(msg="id is required to set vApp property") + + new_vmconfig_spec = vim.vApp.VmConfigSpec() + + # This is primarily for vcsim/integration tests, unset vAppConfig was not seen on my deployments + orig_spec = vm_obj.config.vAppConfig if vm_obj.config.vAppConfig else new_vmconfig_spec + + vapp_properties_current = dict((x.id, x) for x in orig_spec.property) + vapp_properties_to_change = dict((x['id'], x) for x in self.params['vapp_properties']) + + # each property must have a unique key + # init key counter with max value + 1 + all_keys = [x.key for x in orig_spec.property] + new_property_index = max(all_keys) + 1 if all_keys else 0 + + for property_id, property_spec in vapp_properties_to_change.items(): + is_property_changed = False + new_vapp_property_spec = vim.vApp.PropertySpec() + + if property_id in vapp_properties_current: + if property_spec.get('operation') == 'remove': + new_vapp_property_spec.operation = 'remove' + new_vapp_property_spec.removeKey = vapp_properties_current[property_id].key + is_property_changed = True + else: + # this is 'edit' branch + new_vapp_property_spec.operation = 'edit' + new_vapp_property_spec.info = vapp_properties_current[property_id] + try: + for property_name, property_value in property_spec.items(): + + if property_name == 'operation': + # operation is not an info object property + # if set to anything other than 'remove' we don't fail + continue + + # Updating attributes only if needed + if getattr(new_vapp_property_spec.info, property_name) != property_value: + setattr(new_vapp_property_spec.info, property_name, property_value) + is_property_changed = True + + except Exception as e: + self.module.fail_json(msg="Failed to set vApp property field='%s' and value='%s'. Error: %s" + % (property_name, property_value, to_text(e))) + else: + if property_spec.get('operation') == 'remove': + # attemp to delete non-existent property + continue + + # this is add new property branch + new_vapp_property_spec.operation = 'add' + + property_info = vim.vApp.PropertyInfo() + property_info.classId = property_spec.get('classId') + property_info.instanceId = property_spec.get('instanceId') + property_info.id = property_spec.get('id') + property_info.category = property_spec.get('category') + property_info.label = property_spec.get('label') + property_info.type = property_spec.get('type', 'string') + property_info.userConfigurable = property_spec.get('userConfigurable', True) + property_info.defaultValue = property_spec.get('defaultValue') + property_info.value = property_spec.get('value', '') + property_info.description = property_spec.get('description') + + new_vapp_property_spec.info = property_info + new_vapp_property_spec.info.key = new_property_index + new_property_index += 1 + is_property_changed = True + if is_property_changed: + new_vmconfig_spec.property.append(new_vapp_property_spec) + if new_vmconfig_spec.property: + self.configspec.vAppConfig = new_vmconfig_spec + self.change_detected = True + def customize_customvalues(self, vm_obj, config_spec): if len(self.params['customvalues']) == 0: return @@ -1761,6 +1867,7 @@ class PyVmomiHelper(PyVmomi): self.configure_cdrom(vm_obj=self.current_vm_obj) self.customize_customvalues(vm_obj=self.current_vm_obj, config_spec=self.configspec) self.configure_resource_alloc_info(vm_obj=self.current_vm_obj) + self.configure_vapp_properties(vm_obj=self.current_vm_obj) if self.params['annotation'] and self.current_vm_obj.config.annotation != self.params['annotation']: self.configspec.annotation = str(self.params['annotation']) @@ -1892,6 +1999,7 @@ def main(): networks=dict(type='list', default=[]), resource_pool=dict(type='str'), customization=dict(type='dict', default={}, no_log=True), + vapp_properties=dict(type='list', default=[]), ) module = AnsibleModule(argument_spec=argument_spec, diff --git a/test/integration/targets/vmware_guest/tasks/main.yml b/test/integration/targets/vmware_guest/tasks/main.yml index b60d5c93d1..301d1ff1fa 100644 --- a/test/integration/targets/vmware_guest/tasks/main.yml +++ b/test/integration/targets/vmware_guest/tasks/main.yml @@ -31,3 +31,4 @@ # Currently, VCSIM doesn't support DVPG (as portkeys are not available) so commenting this test #- include: network_with_dvpg.yml #- include: template_d1_c1_f0.yml +- include: vapp_d1_c1_f0.yml diff --git a/test/integration/targets/vmware_guest/tasks/vapp_d1_c1_f0.yml b/test/integration/targets/vmware_guest/tasks/vapp_d1_c1_f0.yml new file mode 100644 index 0000000000..8e0d6bc81b --- /dev/null +++ b/test/integration/targets/vmware_guest/tasks/vapp_d1_c1_f0.yml @@ -0,0 +1,107 @@ +- name: Wait for Flask controller to come up online + wait_for: + host: "{{ vcsim }}" + port: 5000 + state: started + +- name: kill vcsim + uri: + url: "{{ 'http://' + vcsim + ':5000/killall' }}" +- name: start vcsim with no folders + uri: + url: "{{ 'http://' + vcsim + ':5000/spawn?datacenter=1&cluster=1&folder=0' }}" + register: vcsim_instance + +- name: Wait for Flask controller to come up online + wait_for: + host: "{{ vcsim }}" + port: 443 + state: started + +- name: get a list of Clusters from vcsim + uri: + url: "{{ 'http://' + vcsim + ':5000/govc_find?filter=CCR' }}" + register: clusterlist + +- debug: var=vcsim_instance +- debug: var=clusterlist + +- name: Create test VM + vmware_guest: + validate_certs: False + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance['json']['username'] }}" + password: "{{ vcsim_instance['json']['password'] }}" + folder: "/{{ (clusterlist['json'][0]|basename).split('_')[0] }}/vm" + name: vApp-Test + datacenter: "{{ (clusterlist['json'][0]|basename).split('_')[0] }}" + cluster: "{{ clusterlist['json'][0] }}" + resource_pool: Resources + guest_id: centos64Guest + hardware: + memory_mb: 512 + num_cpus: 1 + scsi: paravirtual + disk: + - size_mb: 128 + type: thin + datastore: LocalDS_0 + register: vapp_vm + +- debug: var=vapp_vm + +- name: Define vApp properties for the new VM + vmware_guest: + validate_certs: False + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance['json']['username'] }}" + password: "{{ vcsim_instance['json']['password'] }}" + folder: "/{{ (clusterlist['json'][0]|basename).split('_')[0] }}/vm" + name: vApp-Test + datacenter: "{{ (clusterlist['json'][0]|basename).split('_')[0] }}" + vapp_properties: + - id: prop_id1 + category: category + label: prop_label1 + type: string + value: prop_value1 + - id: prop_id2 + category: category + label: prop_label2 + type: string + value: prop_value2 + state: present + register: vapp_vm + +- debug: var=vapp_vm + +- name: assert the vApp propeties were created + assert: + that: + - "vapp_vm.failed == false" + - "vapp_vm.changed == true" + +- name: Edit one vApp property and removing another + vmware_guest: + validate_certs: False + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance['json']['username'] }}" + password: "{{ vcsim_instance['json']['password'] }}" + folder: "/{{ (clusterlist['json'][0]|basename).split('_')[0] }}/vm" + name: vApp-Test + datacenter: "{{ (clusterlist['json'][0]|basename).split('_')[0] }}" + vapp_properties: + - id: prop_id1 + operation: remove + - id: prop_id2 + value: prop_value3 + state: present + register: vapp_vm + +- debug: var=vapp_vm + +- name: assert the VM was changed + assert: + that: + - "vapp_vm.failed == false" + - "vapp_vm.changed == true"