[oVirt] Update/Add functionalities for import storage domain (#26568)

* ovirt_templates: Update the argument spec of templates.

Add id of template since it is needed for register.

* ovirt_vms: Register unregistered VM.

Use register of VM with id instead of name since an
unregitered entity can be registered also without name attribute.

* ovirt_hosts: Add iscsidiscover to ovirt_hosts

Adding functionality of iscsidiscover to be used to discover iscsi
targets.

* ovirt_storage_domains: Add support for import block storage domain.

* Add functionality of partial import to unregistered VMs.

* Add functionality of partial import to unregistered Templates.

* ovirt_hosts: Add iscsilogin to ovirt hosts.

Add functionality of iscsi login to ovirt hosts to be used to connect to
iscsi targets and to be able to import iSCSI storage domain eventually.

* Add ovirt_storage_templates_facts

Adding fact module for storage templates.
The module should help with registering unregistered templates.

* Add ovirt_storage_vms_facts

Adding fact module for storage VMs.
The module should help with registering unregistered VMs.
This commit is contained in:
maorlipchuk 2017-08-23 15:44:02 +03:00 committed by Ryan Brown
parent 2f3d9566f9
commit 07feec30d3
7 changed files with 458 additions and 40 deletions

View file

@ -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

View file

@ -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',

View file

@ -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(

View file

@ -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 <http://www.gnu.org/licenses/>.
#
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()

View file

@ -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 <http://www.gnu.org/licenses/>.
#
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()

View file

@ -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,

View file

@ -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: