diff --git a/lib/ansible/module_utils/ovirt.py b/lib/ansible/module_utils/ovirt.py index 3e001c0d16..bd08315d3a 100644 --- a/lib/ansible/module_utils/ovirt.py +++ b/lib/ansible/module_utils/ovirt.py @@ -746,6 +746,17 @@ class BaseModule(object): 'diff': self._diff, } + def wait_for_import(self): + if self._module.params['wait']: + start = time.time() + timeout = self._module.params['timeout'] + poll_interval = self._module.params['poll_interval'] + while time.time() < start + timeout: + entity = self.search_entity() + if entity: + return entity + time.sleep(poll_interval) + def search_entity(self, search_params=None): """ Always first try to search by `ID`, if ID isn't specified, @@ -755,10 +766,10 @@ class BaseModule(object): entity = None if 'id' in self._module.params and self._module.params['id'] is not None: - entity = search_by_attributes(self._service, id=self._module.params['id']) + entity = get_entity(self._service.service(self._module.params['id'])) elif search_params is not None: entity = search_by_attributes(self._service, **search_params) - elif 'name' in self._module.params and self._module.params['name'] is not None: + elif self._module.params.get('name') is not None: entity = search_by_attributes(self._service, name=self._module.params['name']) return entity diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_hosts.py b/lib/ansible/modules/cloud/ovirt/ovirt_hosts.py index 34437ffbb3..4c343d5dfd 100644 --- a/lib/ansible/modules/cloud/ovirt/ovirt_hosts.py +++ b/lib/ansible/modules/cloud/ovirt/ovirt_hosts.py @@ -40,9 +40,10 @@ options: state: description: - "State which should a host to be in after successful completion." + - "I(iscsilogin) and I(iscsidiscover) are supported since version 2.4." choices: [ 'present', 'absent', 'maintenance', 'upgraded', 'started', - 'restarted', 'stopped', 'reinstalled' + 'restarted', 'stopped', 'reinstalled', 'iscsidiscover', 'iscsilogin' ] default: present comment: @@ -108,6 +109,13 @@ options: - "Enable or disable power management of the host." - "For more comprehensive setup of PM use C(ovirt_host_pm) module." version_added: 2.4 + iscsi: + description: + - "If C(state) is I(iscsidiscover) it means that the iscsi attribute is being + used to discover targets" + - "If C(state) is I(iscsilogin) it means that the iscsi attribute is being + used to login to the specified targets passed as part of the iscsi attribute" + version_added: "2.4" extends_documentation_fragment: ovirt ''' @@ -158,6 +166,29 @@ EXAMPLES = ''' state: upgraded name: myhost +# discover iscsi targets +- ovirt_hosts: + state: iscsidiscover + name: myhost + iscsi: + username: iscsi_user + password: secret + address: 10.34.61.145 + port: 3260 + + +# login to iscsi targets +- ovirt_hosts: + state: iscsilogin + name: myhost + iscsi: + username: iscsi_user + password: secret + address: 10.34.61.145 + target: "iqn.2015-07.com.mlipchuk2.redhat:444" + port: 3260 + + # Reinstall host using public key - ovirt_hosts: state: reinstalled @@ -182,6 +213,10 @@ host: at following url: http://ovirt.github.io/ovirt-engine-api-model/master/#types/host." returned: On success if host is found. type: dict +iscsi_targets: + description: "List of host iscsi targets" + returned: On success if host is found and state is iscsidiscover. + type: list ''' import time @@ -200,6 +235,7 @@ from ansible.module_utils.ovirt import ( check_sdk, create_connection, equal, + get_id_by_name, ovirt_full_argument_spec, wait, ) @@ -327,7 +363,7 @@ def main(): state=dict( choices=[ 'present', 'absent', 'maintenance', 'upgraded', 'started', - 'restarted', 'stopped', 'reinstalled', + 'restarted', 'stopped', 'reinstalled', 'iscsidiscover', 'iscsilogin' ], default='present', ), @@ -346,10 +382,15 @@ def main(): kernel_params=dict(default=None, type='list'), hosted_engine=dict(default=None, choices=['deploy', 'undeploy']), power_management_enabled=dict(default=None, type='bool'), + iscsi=dict(default=None, type='dict'), ) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, + required_if=[ + ['state', 'iscsidiscover', ['iscsi']], + ['state', 'iscsilogin', ['iscsi']] + ] ) check_sdk(module) @@ -396,6 +437,33 @@ def main(): post_action=lambda h: time.sleep(module.params['poll_interval']), fail_condition=failed_state, ) + elif state == 'iscsidiscover': + host_id = get_id_by_name(hosts_service, module.params['name']) + iscsi_targets = hosts_service.service(host_id).iscsi_discover( + iscsi=otypes.IscsiDetails( + port=int(module.params['iscsi']['port']) if module.params['iscsi']['port'].isdigit() else None, + username=module.params['iscsi']['username'], + password=module.params['iscsi']['password'], + address=module.params['iscsi']['address'], + ), + ) + ret = { + 'changed': False, + 'id': host_id, + 'iscsi_targets': iscsi_targets, + } + elif state == 'iscsilogin': + host_id = get_id_by_name(hosts_service, module.params['name']) + ret = hosts_module.action( + action='iscsi_login', + iscsi=otypes.IscsiDetails( + port=int(module.params['iscsi']['port']) if module.params['iscsi']['port'].isdigit() else None, + username=module.params['iscsi']['username'], + password=module.params['iscsi']['password'], + address=module.params['iscsi']['address'], + target=module.params['iscsi']['target'], + ), + ) elif state == 'started': ret = hosts_module.action( action='fence', diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_storage_domains.py b/lib/ansible/modules/cloud/ovirt/ovirt_storage_domains.py index ba914b5b70..45aaef37ef 100644 --- a/lib/ansible/modules/cloud/ovirt/ovirt_storage_domains.py +++ b/lib/ansible/modules/cloud/ovirt/ovirt_storage_domains.py @@ -33,12 +33,17 @@ author: "Ondra Machacek (@machacekondra)" description: - "Module to manage storage domains in oVirt/RHV" options: + id: + description: + - "Id of the storage domain to be imported." + version_added: "2.4" name: description: - "Name of the storage domain to manage." state: description: - - "Should the storage domain be present/absent/maintenance/unattached" + - "Should the storage domain be present/absent/maintenance/unattached/imported" + - "I(imported) is supported since version 2.4." choices: ['present', 'absent', 'maintenance', 'unattached'] default: present description: @@ -256,6 +261,8 @@ class StorageDomainModule(BaseModule): name=self._module.params['name'], description=self._module.params['description'], comment=self._module.params['comment'], + import_=True if (self._module.params['state'] == 'imported' and storage_type in ['iscsi', 'fcp']) else None, + id=self._module.params['id'] if (self._module.params['state'] == 'imported' and storage_type in ['iscsi', 'fcp']) else None, type=otypes.StorageDomainType( self._module.params['domain_function'] ), @@ -423,9 +430,10 @@ def control_state(sd_module): def main(): argument_spec = ovirt_full_argument_spec( state=dict( - choices=['present', 'absent', 'maintenance', 'unattached'], + choices=['present', 'absent', 'maintenance', 'unattached', 'imported'], default='present', ), + id=dict(default=None), name=dict(required=True), description=dict(default=None), comment=dict(default=None), @@ -465,7 +473,7 @@ def main(): format=module.params['format'], host=module.params['host'], ) - elif state == 'present': + elif state == 'present' or state == 'imported': sd_id = storage_domains_module.create()['id'] storage_domains_module.post_create_check(sd_id) ret = storage_domains_module.action( diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_storage_templates_facts.py b/lib/ansible/modules/cloud/ovirt/ovirt_storage_templates_facts.py new file mode 100644 index 0000000000..cacb778b87 --- /dev/null +++ b/lib/ansible/modules/cloud/ovirt/ovirt_storage_templates_facts.py @@ -0,0 +1,123 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 Red Hat, Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: ovirt_storage_templates_facts +short_description: Retrieve facts about one or more oVirt/RHV templates relate to a storage domain. +author: "Maor Lipchuk" +version_added: "2.4" +description: + - "Retrieve facts about one or more oVirt/RHV templates relate to a storage domain." +notes: + - "This module creates a new top-level C(ovirt_storage_templates) fact, which + contains a list of templates." +options: + unregistered: + description: + - "Flag which indicates whether to get unregistered templates which contain one or more + disks which reside on a storage domain or diskless templates." + +extends_documentation_fragment: ovirt_facts +''' + +EXAMPLES = ''' +# Examples don't contain auth parameter for simplicity, +# look at ovirt_auth module to see how to reuse authentication: + +# Gather facts about all Templates which relate to a storage domain and +# are unregistered: +- ovirt_storage_templates_facts: + unregistered=True +- debug: + var: ovirt_storage_templates +''' + +RETURN = ''' +ovirt_storage_templates: + description: "List of dictionaries describing the Templates. Template attribues are mapped to dictionary keys, + all Templates attributes can be found at following url: http://ovirt.github.io/ovirt-engine-api-model/master/#types/template." + returned: On success. + type: list +''' + +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.ovirt import ( + check_sdk, + create_connection, + get_dict_of_struct, + ovirt_facts_full_argument_spec, + get_id_by_name +) + + +def main(): + argument_spec = ovirt_facts_full_argument_spec( + all_content=dict(default=False, type='bool'), + case_sensitive=dict(default=True, type='bool'), + storage_domain=dict(default=None), + max=dict(default=None, type='int'), + unregistered=dict(default=False, type='bool'), + ) + module = AnsibleModule(argument_spec) + check_sdk(module) + + try: + auth = module.params.pop('auth') + connection = create_connection(auth) + storage_domains_service = connection.system_service().storage_domains_service() + sd_id = get_id_by_name(storage_domains_service, module.params['storage_domain']) + storage_domain_service = storage_domains_service.storage_domain_service(sd_id) + templates_service = storage_domain_service.templates_service() + + # Find the the unregistered Template we want to register: + if module.params.get('unregistered'): + templates = templates_service.list(unregistered=True) + else: + templates = templates_service.list() + module.exit_json( + changed=False, + ansible_facts=dict( + ovirt_storage_templates=[ + get_dict_of_struct( + struct=c, + connection=connection, + fetch_nested=module.params.get('fetch_nested'), + attributes=module.params.get('nested_attributes'), + ) for c in templates + ], + ), + ) + except Exception as e: + module.fail_json(msg=str(e), exception=traceback.format_exc()) + finally: + connection.close(logout=auth.get('token') is None) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_storage_vms_facts.py b/lib/ansible/modules/cloud/ovirt/ovirt_storage_vms_facts.py new file mode 100644 index 0000000000..ac3a68f93b --- /dev/null +++ b/lib/ansible/modules/cloud/ovirt/ovirt_storage_vms_facts.py @@ -0,0 +1,122 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 Red Hat, Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: ovirt_storage_vms_facts +short_description: Retrieve facts about one or more oVirt/RHV virtual machines relate to a storage domain. +author: "Maor Lipchuk" +version_added: "2.4" +description: + - "Retrieve facts about one or more oVirt/RHV virtual machines relate to a storage domain." +notes: + - "This module creates a new top-level C(ovirt_storage_vms) fact, which + contains a list of virtual machines." +options: + unregistered: + description: + - "Flag which indicates whether to get unregistered virtual machines which contain one or more + disks which reside on a storage domain or diskless virtual machines." +extends_documentation_fragment: ovirt_facts +''' + +EXAMPLES = ''' +# Examples don't contain auth parameter for simplicity, +# look at ovirt_auth module to see how to reuse authentication: + +# Gather facts about all VMs which relate to a storage domain and +# are unregistered: +- ovirt_vms_facts: + unregistered=True +- debug: + var: ovirt_storage_vms +''' + +RETURN = ''' +ovirt_storage_vms: + description: "List of dictionaries describing the VMs. VM attribues are mapped to dictionary keys, + all VMs attributes can be found at following url: http://ovirt.github.io/ovirt-engine-api-model/master/#types/vm." + returned: On success. + type: list +''' + +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.ovirt import ( + check_sdk, + create_connection, + get_dict_of_struct, + ovirt_facts_full_argument_spec, + get_id_by_name +) + + +def main(): + argument_spec = ovirt_facts_full_argument_spec( + all_content=dict(default=False, type='bool'), + case_sensitive=dict(default=True, type='bool'), + storage_domain=dict(default=None), + max=dict(default=None, type='int'), + unregistered=dict(default=False, type='bool'), + ) + module = AnsibleModule(argument_spec) + check_sdk(module) + + try: + auth = module.params.pop('auth') + connection = create_connection(auth) + storage_domains_service = connection.system_service().storage_domains_service() + sd_id = get_id_by_name(storage_domains_service, module.params['storage_domain']) + storage_domain_service = storage_domains_service.storage_domain_service(sd_id) + vms_service = storage_domain_service.vms_service() + + # Find the the unregistered VM we want to register: + if module.params.get('unregistered'): + vms = vms_service.list(unregistered=True) + else: + vms = vms_service.list() + module.exit_json( + changed=False, + ansible_facts=dict( + ovirt_storage_vms=[ + get_dict_of_struct( + struct=c, + connection=connection, + fetch_nested=module.params.get('fetch_nested'), + attributes=module.params.get('nested_attributes'), + ) for c in vms + ], + ), + ) + except Exception as e: + module.fail_json(msg=str(e), exception=traceback.format_exc()) + finally: + connection.close(logout=auth.get('token') is None) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_templates.py b/lib/ansible/modules/cloud/ovirt/ovirt_templates.py index 10c046ed5f..4051c51a40 100644 --- a/lib/ansible/modules/cloud/ovirt/ovirt_templates.py +++ b/lib/ansible/modules/cloud/ovirt/ovirt_templates.py @@ -36,13 +36,16 @@ options: name: description: - "Name of the template to manage." - required: true + id: + description: + - "ID of the template to be registered." + version_added: "2.4" state: description: - "Should the template be present/absent/exported/imported/registered. When C(state) is I(registered) and the unregistered template's name - belongs to an already registered in engine template then we fail - to register the unregistered template." + belongs to an already registered in engine template in the same DC + then we fail to register the unregistered template." choices: ['present', 'absent', 'exported', 'imported', 'registered'] default: present vm: @@ -57,6 +60,10 @@ options: cluster: description: - "Name of the cluster, where template should be created/imported." + allow_partial_import: + description: + - "Boolean indication whether to allow partial registration of a template when C(state) is registered." + version_added: "2.4" exclusive: description: - "When C(state) is I(exported) this parameter indicates if the existing templates with the @@ -120,9 +127,24 @@ EXAMPLES = ''' # Register template - ovirt_templates: state: registered - name: mytemplate storage_domain: mystorage cluster: mycluster + name: mytemplate + +# Register template using id +- ovirt_templates: + state: registered + storage_domain: mystorage + cluster: mycluster + id: 1111-1111-1111-1111 + +# Register template, allowing partial import +- ovirt_templates: + state: registered + storage_domain: mystorage + allow_partial_import: "True" + cluster: mycluster + id: 1111-1111-1111-1111 # Import image from Glance s a template - ovirt_templates: @@ -216,28 +238,18 @@ class TemplatesModule(BaseModule): self._service = self._connection.system_service().templates_service() -def wait_for_import(module, templates_service): - if module.params['wait']: - start = time.time() - timeout = module.params['timeout'] - poll_interval = module.params['poll_interval'] - while time.time() < start + timeout: - template = search_by_name(templates_service, module.params['name']) - if template: - return template - time.sleep(poll_interval) - - def main(): argument_spec = ovirt_full_argument_spec( state=dict( choices=['present', 'absent', 'exported', 'imported', 'registered'], default='present', ), - name=dict(default=None, required=True), + id=dict(default=None), + name=dict(default=None), vm=dict(default=None), description=dict(default=None), cluster=dict(default=None), + allow_partial_import=dict(default=None, type='bool'), cpu_profile=dict(default=None), disks=dict(default=[], type='list'), clone_permissions=dict(type='bool'), @@ -251,6 +263,7 @@ def main(): module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, + required_one_of=[['id', 'name']], ) check_sdk(module) @@ -328,7 +341,7 @@ def main(): ) if module.params['cluster'] else None, **kwargs ) - template = wait_for_import(module, templates_service) + template = templates_module.wait_for_import() ret = { 'changed': True, 'id': template.id, @@ -344,32 +357,32 @@ def main(): # Find the unregistered Template we want to register: templates = templates_service.list(unregistered=True) template = next( - (t for t in templates if t.name == module.params['name']), + (t for t in templates if (t.id == module.params['id'] or t.name == module.params['name'])), None ) changed = False if template is None: - # Test if template is registered: template = templates_module.search_entity() if template is None: raise ValueError( - "Template with name '%s' wasn't found." % module.params['name'] + "Template '%s(%s)' wasn't found." % (module.params['name'], module.params['id']) ) else: + # Register the template into the system: changed = True template_service = templates_service.template_service(template.id) - # Register the template into the system: template_service.register( + allow_partial_import=module.params['allow_partial_import'], cluster=otypes.Cluster( name=module.params['cluster'] - ) if module.params['cluster'] else None, - template=otypes.Template( - name=module.params['name'], - ), + ) if module.params['cluster'] else None ) - if module.params['wait']: - template = wait_for_import(module, templates_service) + if module.params['wait']: + template = templates_module.wait_for_import() + else: + # Fetch template to initialize return. + template = template_service.get() ret = { 'changed': changed, 'id': template.id, diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_vms.py b/lib/ansible/modules/cloud/ovirt/ovirt_vms.py index 4cf601051d..a38246476a 100644 --- a/lib/ansible/modules/cloud/ovirt/ovirt_vms.py +++ b/lib/ansible/modules/cloud/ovirt/ovirt_vms.py @@ -43,15 +43,23 @@ options: - "ID of the Virtual Machine to manage." state: description: - - "Should the Virtual Machine be running/stopped/present/absent/suspended/next_run." + - "Should the Virtual Machine be running/stopped/present/absent/suspended/next_run/registered. + When C(state) is I(registered) and the unregistered VM's name + belongs to an already registered in engine VM in the same DC + then we fail to register the unregistered template." - "I(present) and I(running) are equal states." - "I(next_run) state updates the VM and if the VM has next run configuration it will be rebooted." - "Please check I(notes) to more detailed description of states." - choices: ['running', 'stopped', 'present', 'absent', 'suspended', 'next_run'] + - "I(registered) is supported since 2.4" + choices: ['running', 'stopped', 'present', 'absent', 'suspended', 'next_run', 'registered'] default: present cluster: description: - "Name of the cluster, where Virtual Machine should be created. Required if creating VM." + allow_partial_import: + description: + - "Boolean indication whether to allow partial registration of Virtual Machine when C(state) is registered." + version_added: "2.4" template: description: - "Name of the template, which should be used to create Virtual Machine. Required if creating VM." @@ -354,6 +362,28 @@ ovirt_vms: name: myvm template: rhel7_template +# Register VM +ovirt_vms: + state: registered + storage_domain: mystorage + cluster: mycluster + name: myvm + +# Register VM using id +ovirt_vms: + state: registered + storage_domain: mystorage + cluster: mycluster + id: 1111-1111-1111-1111 + +# Register VM, allowing partial import +ovirt_vms: + state: registered + storage_domain: mystorage + allow_partial_import: "True" + cluster: mycluster + id: 1111-1111-1111-1111 + # Creates a stateless VM which will always use latest template version: ovirt_vms: name: myvm @@ -522,7 +552,6 @@ vm: returned: On success if VM is found. type: dict ''' - import traceback try: @@ -538,6 +567,7 @@ from ansible.module_utils.ovirt import ( convert_to_bytes, create_connection, equal, + get_dict_of_struct, get_entity, get_link_name, get_id_by_name, @@ -1053,16 +1083,16 @@ def control_state(vm, vms_service, module): condition=lambda vm: vm.status in [otypes.VmStatus.DOWN, otypes.VmStatus.UP], ) - def main(): argument_spec = ovirt_full_argument_spec( state=dict( - choices=['running', 'stopped', 'present', 'absent', 'suspended', 'next_run'], + choices=['running', 'stopped', 'present', 'absent', 'suspended', 'next_run', 'registered'], default='present', ), name=dict(default=None), id=dict(default=None), cluster=dict(default=None), + allow_partial_import=dict(default=None, type='bool'), template=dict(default=None), template_version=dict(default=None, type='int'), use_latest_template_version=dict(default=None, type='bool'), @@ -1119,6 +1149,7 @@ def main(): module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, + required_one_of=[['id', 'name']], ) check_sdk(module) check_params(module) @@ -1243,6 +1274,48 @@ def main(): ) elif state == 'absent': ret = vms_module.remove() + elif state == 'registered': + storage_domains_service = connection.system_service().storage_domains_service() + + # Find the storage domain with unregistered VM: + sd_id = get_id_by_name(storage_domains_service, module.params['storage_domain']) + storage_domain_service = storage_domains_service.storage_domain_service(sd_id) + vms_service = storage_domain_service.vms_service() + + # Find the the unregistered VM we want to register: + vms = vms_service.list(unregistered=True) + vm = next( + (vm for vm in vms if (vm.id == module.params['id'] or vm.name == module.params['name'])), + None + ) + changed = False + if vm is None: + vm = vms_module.search_entity() + if vm is None: + raise ValueError( + "VM '%s(%s)' wasn't found." % (module.params['name'], module.params['id']) + ) + else: + # Register the vm into the system: + changed = True + vm_service = vms_service.vm_service(vm.id) + vm_service.register( + allow_partial_import=module.params['allow_partial_import'], + cluster=otypes.Cluster( + name=module.params['cluster'] + ) if module.params['cluster'] else None + ) + + if module.params['wait']: + vm = vms_module.wait_for_import() + else: + # Fetch vm to initialize return. + vm = vm_service.get() + ret = { + 'changed': changed, + 'id': vm.id, + 'vm': get_dict_of_struct(vm) + } module.exit_json(**ret) except Exception as e: