From ee7f1cde0ef54a9c2db385907fe9ccc19c75ad32 Mon Sep 17 00:00:00 2001 From: Ondra Machacek Date: Thu, 2 Feb 2017 19:50:52 +0100 Subject: [PATCH] ovirt: Add support to diff (#20698) * cloud: ovirt: Add diff support to * cloud: ovirt: Add ability to print nested list of objects * cloud: ovirt: Fix waiting while reinstalling host * cloud: ovirt: fix ovirt_vms 404 error * cloud: ovirt: add check_mode support to ovirt_auth module --- lib/ansible/module_utils/ovirt.py | 119 ++++++++++++------ lib/ansible/modules/cloud/ovirt/ovirt_auth.py | 1 + .../modules/cloud/ovirt/ovirt_hosts.py | 49 +++++--- lib/ansible/modules/cloud/ovirt/ovirt_vms.py | 3 +- .../utils/module_docs_fragments/ovirt.py | 2 +- 5 files changed, 118 insertions(+), 56 deletions(-) diff --git a/lib/ansible/module_utils/ovirt.py b/lib/ansible/module_utils/ovirt.py index d4944da800..f4bb16702e 100644 --- a/lib/ansible/module_utils/ovirt.py +++ b/lib/ansible/module_utils/ovirt.py @@ -18,6 +18,7 @@ # along with Ansible. If not, see . # +import collections import inspect import os import time @@ -55,47 +56,56 @@ def get_dict_of_struct(struct, connection=None, fetch_nested=False, attributes=N """ Convert SDK Struct type into dictionary. """ + res = {} + def remove_underscore(val): if val.startswith('_'): val = val[1:] remove_underscore(val) return val - res = {} + def convert_value(value): + nested = False + + if isinstance(value, sdk.Struct): + return get_dict_of_struct(value) + elif isinstance(value, Enum) or isinstance(value, datetime): + return str(value) + elif isinstance(value, list) or isinstance(value, sdk.List): + if isinstance(value, sdk.List) and fetch_nested and value.href: + try: + value = connection.follow_link(value) + nested = True + except sdk.Error: + value = [] + + ret = [] + for i in value: + if isinstance(i, sdk.Struct): + if not nested: + ret.append(get_dict_of_struct(i)) + else: + nested_obj = dict( + (attr, convert_value(getattr(i, attr))) + for attr in attributes if getattr(i, attr, None) + ) + nested_obj['id'] = getattr(i, 'id', None), + ret.append(nested_obj) + elif isinstance(i, Enum): + ret.append(str(i)) + else: + ret.append(i) + return ret + else: + return value + if struct is not None: for key, value in struct.__dict__.items(): - nested = False - key = remove_underscore(key) if value is None: continue - elif isinstance(value, sdk.Struct): - res[key] = get_dict_of_struct(value) - elif isinstance(value, Enum) or isinstance(value, datetime): - res[key] = str(value) - elif isinstance(value, list) or isinstance(value, sdk.List): - if isinstance(value, sdk.List) and fetch_nested and value.href: - value = connection.follow_link(value) - nested = True - - res[key] = [] - for i in value: - if isinstance(i, sdk.Struct): - if not nested: - res[key].append(get_dict_of_struct(i)) - else: - nested_obj = dict( - (attr, getattr(i, attr)) - for attr in attributes if getattr(i, attr, None) - ) - nested_obj['id'] = getattr(i, 'id', None), - res[key].append(nested_obj) - elif isinstance(i, Enum): - res[key].append(str(i)) - else: - res[key].append(i) - else: - res[key] = value + key = remove_underscore(key) + res[key] = convert_value(value) return res @@ -283,9 +293,6 @@ def wait( if wait: start = time.time() while time.time() < start + timeout: - # Sleep for `poll_interval` seconds if none of the conditions apply: - time.sleep(float(poll_interval)) - # Exit if the condition of entity is valid: entity = get_entity(service) if condition(entity): @@ -293,6 +300,11 @@ def wait( elif fail_condition(entity): raise Exception("Error while waiting on result state of the entity.") + # Sleep for `poll_interval` seconds if none of the conditions apply: + time.sleep(float(poll_interval)) + + raise Exception("Timeout exceed while waiting on result state of the entity.") + def __get_auth_dict(): OVIRT_URL = os.environ.get('OVIRT_URL') @@ -331,7 +343,7 @@ def ovirt_facts_full_argument_spec(**kwargs): spec = dict( auth=__get_auth_dict(), fetch_nested=dict(default=False, type='bool'), - nested_attributes=dict(type='list'), + nested_attributes=dict(type='list', default=list()), ) spec.update(kwargs) return spec @@ -350,7 +362,7 @@ def ovirt_full_argument_spec(**kwargs): wait=dict(default=True, type='bool'), poll_interval=dict(default=3, type='int'), fetch_nested=dict(default=False, type='bool'), - nested_attributes=dict(type='list'), + nested_attributes=dict(type='list', default=list()), ) spec.update(kwargs) return spec @@ -401,6 +413,7 @@ class BaseModule(object): self._module = module self._service = service self._changed = changed + self._diff = {'after': dict(), 'before': dict()} @property def changed(self): @@ -464,6 +477,15 @@ class BaseModule(object): """ pass + def diff_update(self, after, update): + for k, v in update.items(): + if isinstance(v, collections.Mapping): + after[k] = self.diff_update(after.get(k, dict()), v) + else: + after[k] = update[k] + return after + + def create(self, entity=None, result_state=None, fail_condition=lambda e: False, search_params=None, **kwargs): """ Method which is called when state of the entity is 'present'. If user @@ -492,9 +514,25 @@ class BaseModule(object): # Entity exists, so update it: entity_service = self._service.service(entity.id) if not self.update_check(entity): + new_entity = self.build_entity() if not self._module.check_mode: - entity_service.update(self.build_entity()) + updated_entity = entity_service.update(new_entity) self.post_update(entity) + + # Update diffs only if user specified --diff paramter, + # so we don't useless overload API: + if self._module._diff: + before = get_dict_of_struct( + entity, + self._connection, + fetch_nested=True, + attributes=['name'], + ) + after = before.copy() + self.diff_update(after, get_dict_of_struct(new_entity)) + self._diff['before'] = before + self._diff['after'] = after + self.changed = True else: # Entity don't exists, so create it: @@ -530,6 +568,7 @@ class BaseModule(object): fetch_nested=self._module.params.get('fetch_nested'), attributes=self._module.params.get('nested_attributes'), ), + 'diff': self._diff, } def pre_remove(self, entity): @@ -540,6 +579,12 @@ class BaseModule(object): """ pass + def entity_name(self, entity): + return "{e_type} '{e_name}'".format( + e_type=type(entity).__name__.lower(), + e_name=getattr(entity, 'name', None), + ) + def remove(self, entity=None, search_params=None, **kwargs): """ Method which is called when state of the entity is 'absent'. If user @@ -568,7 +613,6 @@ class BaseModule(object): entity_service = self._service.service(entity.id) if not self._module.check_mode: entity_service.remove(**kwargs) - wait( service=entity_service, condition=lambda entity: not entity, @@ -662,6 +706,7 @@ class BaseModule(object): fetch_nested=self._module.params.get('fetch_nested'), attributes=self._module.params.get('nested_attributes'), ), + 'diff': self._diff, } def search_entity(self, search_params=None): diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_auth.py b/lib/ansible/modules/cloud/ovirt/ovirt_auth.py index f0597c7e14..7fed7a2adf 100644 --- a/lib/ansible/modules/cloud/ovirt/ovirt_auth.py +++ b/lib/ansible/modules/cloud/ovirt/ovirt_auth.py @@ -190,6 +190,7 @@ def main(): ('state', 'absent', ['ovirt_auth']), ('state', 'present', ['username', 'password', 'url']), ], + supports_check_mode=True, ) check_sdk(module) diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_hosts.py b/lib/ansible/modules/cloud/ovirt/ovirt_hosts.py index 06bfa1824b..e2e7c29bb7 100644 --- a/lib/ansible/modules/cloud/ovirt/ovirt_hosts.py +++ b/lib/ansible/modules/cloud/ovirt/ovirt_hosts.py @@ -19,6 +19,7 @@ # along with Ansible. If not, see . # +import time import traceback try: @@ -152,6 +153,12 @@ EXAMPLES = ''' state: upgraded name: myhost +# Reinstall host using public key +- ovirt_hosts: + state: reinstalled + name: myhost + public_key: true + # Remove host - ovirt_hosts: state: absent @@ -184,7 +191,7 @@ class HostsModule(BaseModule): address=self._module.params['address'], root_password=self._module.params['password'], ssh=otypes.Ssh( - authentication_method='publickey', + authentication_method=otypes.SshAuthenticationMethod.PUBLICKEY, ) if self._module.params['public_key'] else None, kdump_status=otypes.KdumpStatus( self._module.params['kdump_integration'] @@ -228,6 +235,15 @@ class HostsModule(BaseModule): self._service.host_service(entity.id).activate() self.changed = True + def post_reinstall(self, host): + wait( + service=self._service.service(host.id), + condition=lambda h: h.status != hoststate.MAINTENANCE, + fail_condition=failed_state, + wait=self._module.params['wait'], + timeout=self._module.params['timeout'], + ) + def failed_state(host): return host.status in [ @@ -315,23 +331,23 @@ def main(): state = module.params['state'] control_state(hosts_module) if state == 'present': - ret = hosts_module.create() - ret['changed'] = hosts_module.action( + hosts_module.create() + ret = hosts_module.action( action='activate', action_condition=lambda h: h.status == hoststate.MAINTENANCE, wait_condition=lambda h: h.status == hoststate.UP, fail_condition=failed_state, - )['changed'] or ret['changed'] + ) elif state == 'absent': ret = hosts_module.remove() elif state == 'maintenance': - ret = hosts_module.action( + hosts_module.action( action='deactivate', action_condition=lambda h: h.status != hoststate.MAINTENANCE, wait_condition=lambda h: h.status == hoststate.MAINTENANCE, fail_condition=failed_state, ) - ret['changed'] = hosts_module.create()['changed'] or ret['changed'] + ret = hosts_module.create() elif state == 'upgraded': ret = hosts_module.action( action='upgrade', @@ -348,19 +364,19 @@ def main(): fence_type='start', ) elif state == 'stopped': - ret = hosts_module.action( + hosts_module.action( action='deactivate', action_condition=lambda h: h.status not in [hoststate.MAINTENANCE, hoststate.DOWN], wait_condition=lambda h: h.status in [hoststate.MAINTENANCE, hoststate.DOWN], fail_condition=failed_state, ) - ret['changed'] = hosts_module.action( + ret = hosts_module.action( action='fence', action_condition=lambda h: h.status != hoststate.DOWN, - wait_condition=lambda h: h.status == hoststate.DOWN, + wait_condition=lambda h: h.status == hoststate.DOWN if module.params['wait'] else True, fail_condition=failed_state, fence_type='stop', - )['changed'] or ret['changed'] + ) elif state == 'restarted': ret = hosts_module.action( action='fence', @@ -370,17 +386,18 @@ def main(): ) elif state == 'reinstalled': # Deactivate host if not in maintanence: - deactivate_changed = hosts_module.action( + hosts_module.action( action='deactivate', action_condition=lambda h: h.status not in [hoststate.MAINTENANCE, hoststate.DOWN], wait_condition=lambda h: h.status in [hoststate.MAINTENANCE, hoststate.DOWN], fail_condition=failed_state, - )['changed'] + ) # Reinstall host: - install_changed = hosts_module.action( + hosts_module.action( action='install', action_condition=lambda h: h.status == hoststate.MAINTENANCE, + post_action=hosts_module.post_reinstall, wait_condition=lambda h: h.status == hoststate.MAINTENANCE, fail_condition=failed_state, host=otypes.Host( @@ -388,9 +405,9 @@ def main(): ) if module.params['override_iptables'] else None, root_password=module.params['password'], ssh=otypes.Ssh( - authentication_method='publickey', + authentication_method=otypes.SshAuthenticationMethod.PUBLICKEY, ) if module.params['public_key'] else None, - )['changed'] + ) # Activate host after reinstall: ret = hosts_module.action( @@ -399,8 +416,6 @@ def main(): wait_condition=lambda h: h.status == hoststate.UP, fail_condition=failed_state, ) - ret['changed'] = install_changed or deactivate_changed or ret['changed'] - module.exit_json(**ret) except Exception as e: diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_vms.py b/lib/ansible/modules/cloud/ovirt/ovirt_vms.py index 6e5e16f4a8..ef6880d4af 100644 --- a/lib/ansible/modules/cloud/ovirt/ovirt_vms.py +++ b/lib/ansible/modules/cloud/ovirt/ovirt_vms.py @@ -34,6 +34,7 @@ from ansible.module_utils.ovirt import ( convert_to_bytes, create_connection, equal, + get_entity, get_link_name, ovirt_full_argument_spec, search_by_name, @@ -627,7 +628,7 @@ class VmsModule(BaseModule): # Attach disk to VM: disk_attachments_service = self._service.service(entity.id).disk_attachments_service() - if disk_attachments_service.attachment_service(disk_id).get() is None: + if get_entity(disk_attachments_service.attachment_service(disk_id)) is None: if not self._module.check_mode: disk_attachments_service.add( otypes.DiskAttachment( diff --git a/lib/ansible/utils/module_docs_fragments/ovirt.py b/lib/ansible/utils/module_docs_fragments/ovirt.py index d469ef83c1..932ddb4ad1 100644 --- a/lib/ansible/utils/module_docs_fragments/ovirt.py +++ b/lib/ansible/utils/module_docs_fragments/ovirt.py @@ -69,5 +69,5 @@ requirements: notes: - "In order to use this module you have to install oVirt Python SDK. To ensure it's installed with correct version you can create the following task: - pip: name=ovirt-engine-sdk-python version=4.0.0" + I(pip: name=ovirt-engine-sdk-python version=4.0.0)" '''