mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-10-11 02:44:02 -07:00
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
This commit is contained in:
parent
6ca5fb49c3
commit
ee7f1cde0e
5 changed files with 118 additions and 56 deletions
|
@ -18,6 +18,7 @@
|
||||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import collections
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
import time
|
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.
|
Convert SDK Struct type into dictionary.
|
||||||
"""
|
"""
|
||||||
|
res = {}
|
||||||
|
|
||||||
def remove_underscore(val):
|
def remove_underscore(val):
|
||||||
if val.startswith('_'):
|
if val.startswith('_'):
|
||||||
val = val[1:]
|
val = val[1:]
|
||||||
remove_underscore(val)
|
remove_underscore(val)
|
||||||
return val
|
return val
|
||||||
|
|
||||||
res = {}
|
def convert_value(value):
|
||||||
if struct is not None:
|
|
||||||
for key, value in struct.__dict__.items():
|
|
||||||
nested = False
|
nested = False
|
||||||
key = remove_underscore(key)
|
|
||||||
if value is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
elif isinstance(value, sdk.Struct):
|
if isinstance(value, sdk.Struct):
|
||||||
res[key] = get_dict_of_struct(value)
|
return get_dict_of_struct(value)
|
||||||
elif isinstance(value, Enum) or isinstance(value, datetime):
|
elif isinstance(value, Enum) or isinstance(value, datetime):
|
||||||
res[key] = str(value)
|
return str(value)
|
||||||
elif isinstance(value, list) or isinstance(value, sdk.List):
|
elif isinstance(value, list) or isinstance(value, sdk.List):
|
||||||
if isinstance(value, sdk.List) and fetch_nested and value.href:
|
if isinstance(value, sdk.List) and fetch_nested and value.href:
|
||||||
|
try:
|
||||||
value = connection.follow_link(value)
|
value = connection.follow_link(value)
|
||||||
nested = True
|
nested = True
|
||||||
|
except sdk.Error:
|
||||||
|
value = []
|
||||||
|
|
||||||
res[key] = []
|
ret = []
|
||||||
for i in value:
|
for i in value:
|
||||||
if isinstance(i, sdk.Struct):
|
if isinstance(i, sdk.Struct):
|
||||||
if not nested:
|
if not nested:
|
||||||
res[key].append(get_dict_of_struct(i))
|
ret.append(get_dict_of_struct(i))
|
||||||
else:
|
else:
|
||||||
nested_obj = dict(
|
nested_obj = dict(
|
||||||
(attr, getattr(i, attr))
|
(attr, convert_value(getattr(i, attr)))
|
||||||
for attr in attributes if getattr(i, attr, None)
|
for attr in attributes if getattr(i, attr, None)
|
||||||
)
|
)
|
||||||
nested_obj['id'] = getattr(i, 'id', None),
|
nested_obj['id'] = getattr(i, 'id', None),
|
||||||
res[key].append(nested_obj)
|
ret.append(nested_obj)
|
||||||
elif isinstance(i, Enum):
|
elif isinstance(i, Enum):
|
||||||
res[key].append(str(i))
|
ret.append(str(i))
|
||||||
else:
|
else:
|
||||||
res[key].append(i)
|
ret.append(i)
|
||||||
|
return ret
|
||||||
else:
|
else:
|
||||||
res[key] = value
|
return value
|
||||||
|
|
||||||
|
if struct is not None:
|
||||||
|
for key, value in struct.__dict__.items():
|
||||||
|
if value is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = remove_underscore(key)
|
||||||
|
res[key] = convert_value(value)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@ -283,9 +293,6 @@ def wait(
|
||||||
if wait:
|
if wait:
|
||||||
start = time.time()
|
start = time.time()
|
||||||
while time.time() < start + timeout:
|
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:
|
# Exit if the condition of entity is valid:
|
||||||
entity = get_entity(service)
|
entity = get_entity(service)
|
||||||
if condition(entity):
|
if condition(entity):
|
||||||
|
@ -293,6 +300,11 @@ def wait(
|
||||||
elif fail_condition(entity):
|
elif fail_condition(entity):
|
||||||
raise Exception("Error while waiting on result state of the 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():
|
def __get_auth_dict():
|
||||||
OVIRT_URL = os.environ.get('OVIRT_URL')
|
OVIRT_URL = os.environ.get('OVIRT_URL')
|
||||||
|
@ -331,7 +343,7 @@ def ovirt_facts_full_argument_spec(**kwargs):
|
||||||
spec = dict(
|
spec = dict(
|
||||||
auth=__get_auth_dict(),
|
auth=__get_auth_dict(),
|
||||||
fetch_nested=dict(default=False, type='bool'),
|
fetch_nested=dict(default=False, type='bool'),
|
||||||
nested_attributes=dict(type='list'),
|
nested_attributes=dict(type='list', default=list()),
|
||||||
)
|
)
|
||||||
spec.update(kwargs)
|
spec.update(kwargs)
|
||||||
return spec
|
return spec
|
||||||
|
@ -350,7 +362,7 @@ def ovirt_full_argument_spec(**kwargs):
|
||||||
wait=dict(default=True, type='bool'),
|
wait=dict(default=True, type='bool'),
|
||||||
poll_interval=dict(default=3, type='int'),
|
poll_interval=dict(default=3, type='int'),
|
||||||
fetch_nested=dict(default=False, type='bool'),
|
fetch_nested=dict(default=False, type='bool'),
|
||||||
nested_attributes=dict(type='list'),
|
nested_attributes=dict(type='list', default=list()),
|
||||||
)
|
)
|
||||||
spec.update(kwargs)
|
spec.update(kwargs)
|
||||||
return spec
|
return spec
|
||||||
|
@ -401,6 +413,7 @@ class BaseModule(object):
|
||||||
self._module = module
|
self._module = module
|
||||||
self._service = service
|
self._service = service
|
||||||
self._changed = changed
|
self._changed = changed
|
||||||
|
self._diff = {'after': dict(), 'before': dict()}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def changed(self):
|
def changed(self):
|
||||||
|
@ -464,6 +477,15 @@ class BaseModule(object):
|
||||||
"""
|
"""
|
||||||
pass
|
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):
|
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
|
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 exists, so update it:
|
||||||
entity_service = self._service.service(entity.id)
|
entity_service = self._service.service(entity.id)
|
||||||
if not self.update_check(entity):
|
if not self.update_check(entity):
|
||||||
|
new_entity = self.build_entity()
|
||||||
if not self._module.check_mode:
|
if not self._module.check_mode:
|
||||||
entity_service.update(self.build_entity())
|
updated_entity = entity_service.update(new_entity)
|
||||||
self.post_update(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
|
self.changed = True
|
||||||
else:
|
else:
|
||||||
# Entity don't exists, so create it:
|
# Entity don't exists, so create it:
|
||||||
|
@ -530,6 +568,7 @@ class BaseModule(object):
|
||||||
fetch_nested=self._module.params.get('fetch_nested'),
|
fetch_nested=self._module.params.get('fetch_nested'),
|
||||||
attributes=self._module.params.get('nested_attributes'),
|
attributes=self._module.params.get('nested_attributes'),
|
||||||
),
|
),
|
||||||
|
'diff': self._diff,
|
||||||
}
|
}
|
||||||
|
|
||||||
def pre_remove(self, entity):
|
def pre_remove(self, entity):
|
||||||
|
@ -540,6 +579,12 @@ class BaseModule(object):
|
||||||
"""
|
"""
|
||||||
pass
|
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):
|
def remove(self, entity=None, search_params=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Method which is called when state of the entity is 'absent'. If user
|
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)
|
entity_service = self._service.service(entity.id)
|
||||||
if not self._module.check_mode:
|
if not self._module.check_mode:
|
||||||
entity_service.remove(**kwargs)
|
entity_service.remove(**kwargs)
|
||||||
|
|
||||||
wait(
|
wait(
|
||||||
service=entity_service,
|
service=entity_service,
|
||||||
condition=lambda entity: not entity,
|
condition=lambda entity: not entity,
|
||||||
|
@ -662,6 +706,7 @@ class BaseModule(object):
|
||||||
fetch_nested=self._module.params.get('fetch_nested'),
|
fetch_nested=self._module.params.get('fetch_nested'),
|
||||||
attributes=self._module.params.get('nested_attributes'),
|
attributes=self._module.params.get('nested_attributes'),
|
||||||
),
|
),
|
||||||
|
'diff': self._diff,
|
||||||
}
|
}
|
||||||
|
|
||||||
def search_entity(self, search_params=None):
|
def search_entity(self, search_params=None):
|
||||||
|
|
|
@ -190,6 +190,7 @@ def main():
|
||||||
('state', 'absent', ['ovirt_auth']),
|
('state', 'absent', ['ovirt_auth']),
|
||||||
('state', 'present', ['username', 'password', 'url']),
|
('state', 'present', ['username', 'password', 'url']),
|
||||||
],
|
],
|
||||||
|
supports_check_mode=True,
|
||||||
)
|
)
|
||||||
check_sdk(module)
|
check_sdk(module)
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -152,6 +153,12 @@ EXAMPLES = '''
|
||||||
state: upgraded
|
state: upgraded
|
||||||
name: myhost
|
name: myhost
|
||||||
|
|
||||||
|
# Reinstall host using public key
|
||||||
|
- ovirt_hosts:
|
||||||
|
state: reinstalled
|
||||||
|
name: myhost
|
||||||
|
public_key: true
|
||||||
|
|
||||||
# Remove host
|
# Remove host
|
||||||
- ovirt_hosts:
|
- ovirt_hosts:
|
||||||
state: absent
|
state: absent
|
||||||
|
@ -184,7 +191,7 @@ class HostsModule(BaseModule):
|
||||||
address=self._module.params['address'],
|
address=self._module.params['address'],
|
||||||
root_password=self._module.params['password'],
|
root_password=self._module.params['password'],
|
||||||
ssh=otypes.Ssh(
|
ssh=otypes.Ssh(
|
||||||
authentication_method='publickey',
|
authentication_method=otypes.SshAuthenticationMethod.PUBLICKEY,
|
||||||
) if self._module.params['public_key'] else None,
|
) if self._module.params['public_key'] else None,
|
||||||
kdump_status=otypes.KdumpStatus(
|
kdump_status=otypes.KdumpStatus(
|
||||||
self._module.params['kdump_integration']
|
self._module.params['kdump_integration']
|
||||||
|
@ -228,6 +235,15 @@ class HostsModule(BaseModule):
|
||||||
self._service.host_service(entity.id).activate()
|
self._service.host_service(entity.id).activate()
|
||||||
self.changed = True
|
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):
|
def failed_state(host):
|
||||||
return host.status in [
|
return host.status in [
|
||||||
|
@ -315,23 +331,23 @@ def main():
|
||||||
state = module.params['state']
|
state = module.params['state']
|
||||||
control_state(hosts_module)
|
control_state(hosts_module)
|
||||||
if state == 'present':
|
if state == 'present':
|
||||||
ret = hosts_module.create()
|
hosts_module.create()
|
||||||
ret['changed'] = hosts_module.action(
|
ret = hosts_module.action(
|
||||||
action='activate',
|
action='activate',
|
||||||
action_condition=lambda h: h.status == hoststate.MAINTENANCE,
|
action_condition=lambda h: h.status == hoststate.MAINTENANCE,
|
||||||
wait_condition=lambda h: h.status == hoststate.UP,
|
wait_condition=lambda h: h.status == hoststate.UP,
|
||||||
fail_condition=failed_state,
|
fail_condition=failed_state,
|
||||||
)['changed'] or ret['changed']
|
)
|
||||||
elif state == 'absent':
|
elif state == 'absent':
|
||||||
ret = hosts_module.remove()
|
ret = hosts_module.remove()
|
||||||
elif state == 'maintenance':
|
elif state == 'maintenance':
|
||||||
ret = hosts_module.action(
|
hosts_module.action(
|
||||||
action='deactivate',
|
action='deactivate',
|
||||||
action_condition=lambda h: h.status != hoststate.MAINTENANCE,
|
action_condition=lambda h: h.status != hoststate.MAINTENANCE,
|
||||||
wait_condition=lambda h: h.status == hoststate.MAINTENANCE,
|
wait_condition=lambda h: h.status == hoststate.MAINTENANCE,
|
||||||
fail_condition=failed_state,
|
fail_condition=failed_state,
|
||||||
)
|
)
|
||||||
ret['changed'] = hosts_module.create()['changed'] or ret['changed']
|
ret = hosts_module.create()
|
||||||
elif state == 'upgraded':
|
elif state == 'upgraded':
|
||||||
ret = hosts_module.action(
|
ret = hosts_module.action(
|
||||||
action='upgrade',
|
action='upgrade',
|
||||||
|
@ -348,19 +364,19 @@ def main():
|
||||||
fence_type='start',
|
fence_type='start',
|
||||||
)
|
)
|
||||||
elif state == 'stopped':
|
elif state == 'stopped':
|
||||||
ret = hosts_module.action(
|
hosts_module.action(
|
||||||
action='deactivate',
|
action='deactivate',
|
||||||
action_condition=lambda h: h.status not in [hoststate.MAINTENANCE, hoststate.DOWN],
|
action_condition=lambda h: h.status not in [hoststate.MAINTENANCE, hoststate.DOWN],
|
||||||
wait_condition=lambda h: h.status in [hoststate.MAINTENANCE, hoststate.DOWN],
|
wait_condition=lambda h: h.status in [hoststate.MAINTENANCE, hoststate.DOWN],
|
||||||
fail_condition=failed_state,
|
fail_condition=failed_state,
|
||||||
)
|
)
|
||||||
ret['changed'] = hosts_module.action(
|
ret = hosts_module.action(
|
||||||
action='fence',
|
action='fence',
|
||||||
action_condition=lambda h: h.status != hoststate.DOWN,
|
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,
|
fail_condition=failed_state,
|
||||||
fence_type='stop',
|
fence_type='stop',
|
||||||
)['changed'] or ret['changed']
|
)
|
||||||
elif state == 'restarted':
|
elif state == 'restarted':
|
||||||
ret = hosts_module.action(
|
ret = hosts_module.action(
|
||||||
action='fence',
|
action='fence',
|
||||||
|
@ -370,17 +386,18 @@ def main():
|
||||||
)
|
)
|
||||||
elif state == 'reinstalled':
|
elif state == 'reinstalled':
|
||||||
# Deactivate host if not in maintanence:
|
# Deactivate host if not in maintanence:
|
||||||
deactivate_changed = hosts_module.action(
|
hosts_module.action(
|
||||||
action='deactivate',
|
action='deactivate',
|
||||||
action_condition=lambda h: h.status not in [hoststate.MAINTENANCE, hoststate.DOWN],
|
action_condition=lambda h: h.status not in [hoststate.MAINTENANCE, hoststate.DOWN],
|
||||||
wait_condition=lambda h: h.status in [hoststate.MAINTENANCE, hoststate.DOWN],
|
wait_condition=lambda h: h.status in [hoststate.MAINTENANCE, hoststate.DOWN],
|
||||||
fail_condition=failed_state,
|
fail_condition=failed_state,
|
||||||
)['changed']
|
)
|
||||||
|
|
||||||
# Reinstall host:
|
# Reinstall host:
|
||||||
install_changed = hosts_module.action(
|
hosts_module.action(
|
||||||
action='install',
|
action='install',
|
||||||
action_condition=lambda h: h.status == hoststate.MAINTENANCE,
|
action_condition=lambda h: h.status == hoststate.MAINTENANCE,
|
||||||
|
post_action=hosts_module.post_reinstall,
|
||||||
wait_condition=lambda h: h.status == hoststate.MAINTENANCE,
|
wait_condition=lambda h: h.status == hoststate.MAINTENANCE,
|
||||||
fail_condition=failed_state,
|
fail_condition=failed_state,
|
||||||
host=otypes.Host(
|
host=otypes.Host(
|
||||||
|
@ -388,9 +405,9 @@ def main():
|
||||||
) if module.params['override_iptables'] else None,
|
) if module.params['override_iptables'] else None,
|
||||||
root_password=module.params['password'],
|
root_password=module.params['password'],
|
||||||
ssh=otypes.Ssh(
|
ssh=otypes.Ssh(
|
||||||
authentication_method='publickey',
|
authentication_method=otypes.SshAuthenticationMethod.PUBLICKEY,
|
||||||
) if module.params['public_key'] else None,
|
) if module.params['public_key'] else None,
|
||||||
)['changed']
|
)
|
||||||
|
|
||||||
# Activate host after reinstall:
|
# Activate host after reinstall:
|
||||||
ret = hosts_module.action(
|
ret = hosts_module.action(
|
||||||
|
@ -399,8 +416,6 @@ def main():
|
||||||
wait_condition=lambda h: h.status == hoststate.UP,
|
wait_condition=lambda h: h.status == hoststate.UP,
|
||||||
fail_condition=failed_state,
|
fail_condition=failed_state,
|
||||||
)
|
)
|
||||||
ret['changed'] = install_changed or deactivate_changed or ret['changed']
|
|
||||||
|
|
||||||
|
|
||||||
module.exit_json(**ret)
|
module.exit_json(**ret)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
@ -34,6 +34,7 @@ from ansible.module_utils.ovirt import (
|
||||||
convert_to_bytes,
|
convert_to_bytes,
|
||||||
create_connection,
|
create_connection,
|
||||||
equal,
|
equal,
|
||||||
|
get_entity,
|
||||||
get_link_name,
|
get_link_name,
|
||||||
ovirt_full_argument_spec,
|
ovirt_full_argument_spec,
|
||||||
search_by_name,
|
search_by_name,
|
||||||
|
@ -627,7 +628,7 @@ class VmsModule(BaseModule):
|
||||||
|
|
||||||
# Attach disk to VM:
|
# Attach disk to VM:
|
||||||
disk_attachments_service = self._service.service(entity.id).disk_attachments_service()
|
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:
|
if not self._module.check_mode:
|
||||||
disk_attachments_service.add(
|
disk_attachments_service.add(
|
||||||
otypes.DiskAttachment(
|
otypes.DiskAttachment(
|
||||||
|
|
|
@ -69,5 +69,5 @@ requirements:
|
||||||
notes:
|
notes:
|
||||||
- "In order to use this module you have to install oVirt Python SDK.
|
- "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:
|
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)"
|
||||||
'''
|
'''
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue