diff --git a/lib/ansible/module_utils/ovirt.py b/lib/ansible/module_utils/ovirt.py index 84bfdd0e392..d4944da800c 100644 --- a/lib/ansible/module_utils/ovirt.py +++ b/lib/ansible/module_utils/ovirt.py @@ -364,6 +364,29 @@ def check_params(module): module.fail_json(msg='"name" or "id" is required') +def engine_version(connection): + """ + Return string representation of oVirt engine version. + """ + engine_api = connection.system_service().get() + engine_version = engine_api.product_info.version + return '%s.%s' % (engine_version.major, engine_version.minor) + + +def check_support(version, connection, module, params): + """ + Check if parameters used by user are supported by oVirt Python SDK + and oVirt engine. + """ + api_version = LooseVersion(engine_version(connection)) + version = LooseVersion(version) + for param in params: + if module.params.get(param) is not None: + return LooseVersion(sdk_version.VERSION) >= version and api_version >= version + + return True + + class BaseModule(object): """ This is base class for oVirt modules. oVirt modules should inherit this diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_affinity_groups.py b/lib/ansible/modules/cloud/ovirt/ovirt_affinity_groups.py new file mode 100644 index 00000000000..76b05d24266 --- /dev/null +++ b/lib/ansible/modules/cloud/ovirt/ovirt_affinity_groups.py @@ -0,0 +1,305 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016 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 . +# + +import traceback + +try: + import ovirtsdk4.types as otypes +except ImportError: + pass + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.ovirt import ( + BaseModule, + check_sdk, + check_support, + create_connection, + equal, + ovirt_full_argument_spec, + search_by_name, +) + + +ANSIBLE_METADATA = {'status': ['preview'], + 'supported_by': 'community', + 'version': '1.0'} + +DOCUMENTATION = ''' +--- +module: ovirt_affinity_groups +short_description: Module to manage affinity groups in oVirt +version_added: "2.3" +author: "Ondra Machacek (@machacekondra)" +description: + - "This module manage affinity groups in oVirt. It can also manage assignments + of those groups to VMs." +options: + name: + description: + - "Name of the the affinity group to manage." + required: true + state: + description: + - "Should the affinity group be present or absent." + choices: ['present', 'absent'] + default: present + cluster: + description: + - "Name of the cluster of the affinity group." + description: + description: + - "Description of the affinity group." + host_enforcing: + description: + - "If I(true) VM cannot start on host if it does not satisfy the C(host_rule)." + - "C(This parameter is support since oVirt 4.1 version.)" + host_rule: + description: + - "If I(positive) I(all) VMs in this group should run on the this host." + - "If I(negative) I(no) VMs in this group should run on the this host." + - "C(This parameter is support since oVirt 4.1 version.)" + choices: + - positive + - negative + vm_enforcing: + description: + - "If I(true) VM cannot start if it does not satisfy the C(vm_rule)." + vm_rule: + description: + - "If I(positive) I(all) VMs in this group should run on the host defined by C(host_rule)." + - "If I(negative) I(no) VMs in this group should run on the host defined by C(host_rule)." + - "If I(disabled) this affinity group doesn't take effect." + choices: + - positive + - negative + - disabled + vms: + description: + - "List of the VMs names, which should have assigned this affinity group." + hosts: + description: + - "List of the hosts names, which should have assigned this affinity group." + - "C(This parameter is support since oVirt 4.1 version.)" +extends_documentation_fragment: ovirt +''' + +EXAMPLES = ''' +# Examples don't contain auth parameter for simplicity, +# look at ovirt_auth module to see how to reuse authentication: + +# Create(if not exists) and assign affinity group to VMs vm1 and vm2 and host host1 +- ovirt_affinity_groups: + name: mygroup + cluster: mycluster + vm_enforcing: true + vm_rule: positive + host_enforcing: true + host_rule: positive + vms: + - vm1 + - vm2 + hosts: + - host1 + +# Detach VMs from affinity group and disable VM rule: +- ovirt_affinity_groups: + name: mygroup + cluster: mycluster + vm_enforcing: false + vm_rule: disabled + host_enforcing: true + host_rule: positive + vms: [] + hosts: + - host1 + - host2 + +# Remove affinity group +- ovirt_affinity_groups: + state: absent + cluster: mycluster + name: mygroup +''' + +RETURN = ''' +id: + description: ID of the affinity group which is managed + returned: On success if affinity group is found. + type: str + sample: 7de90f31-222c-436c-a1ca-7e655bd5b60c +affinity_group: + description: "Dictionary of all the affinity group attributes. Affinity group attributes can be found on your oVirt instance + at following url: https://ovirt.example.com/ovirt-engine/api/model#types/affinity_group." + returned: On success if affinity group is found. +''' + + +class AffinityGroupsModule(BaseModule): + + def __init__(self, vm_ids, host_ids, *args, **kwargs): + super(AffinityGroupsModule, self).__init__(*args, **kwargs) + self._vm_ids = vm_ids + self._host_ids = host_ids + + def build_entity(self): + return otypes.AffinityGroup( + name=self._module.params['name'], + description=self._module.params['description'], + positive=( + self._module.params['vm_rule'] == 'positive' + ) if self._module.params['vm_rule'] is not None else None, + enforcing=( + self._module.params['vm_enforcing'] + ) if self._module.params['vm_enforcing'] is not None else None, + vms=[ + otypes.Vm(id=vm_id) for vm_id in self._vm_ids + ] if self._vm_ids is not None else None, + hosts=[ + otypes.Host(id=host_id) for host_id in self._host_ids + ] if self._host_ids is not None else None, + vms_rule=otypes.AffinityRule( + positive=( + self._module.params['vm_rule'] == 'positive' + ) if self._module.params['vm_rule'] is not None else None, + enforcing=self._module.params['vm_enforcing'], + enabled=( + self._module.params['vm_rule'] in ['negative', 'positive'] + ) if self._module.params['vm_rule'] is not None else None, + ) if ( + self._module.params['vm_enforcing'] is not None or + self._module.params['vm_rule'] is not None + ) else None, + hosts_rule=otypes.AffinityRule( + positive=( + self._module.params['host_rule'] == 'positive' + ) if self._module.params['host_rule'] is not None else None, + enforcing=self._module.params['host_enforcing'], + ) if ( + self._module.params['host_enforcing'] is not None or + self._module.params['host_rule'] is not None + ) else None, + ) + + def update_check(self, entity): + assigned_vms = sorted([vm.id for vm in entity.vms]) + assigned_hosts = sorted([host.id for host in entity.hosts]) + + return ( + equal(self._module.params.get('description'), entity.description) and + equal(self._module.params.get('vm_enforcing'), entity.vms_rule.enforcing) and + equal(self._module.params.get('host_enforcing'), entity.hosts_rule.enforcing) and + equal(self._module.params.get('vm_rule') == 'positive', entity.vms_rule.positive) and + equal(self._module.params.get('vm_rule') in ['negative', 'positive'], entity.vms_rule.enabled) and + equal(self._module.params.get('host_rule') == 'positive', entity.hosts_rule.positive) and + equal(self._vm_ids, assigned_vms) and + equal(self._host_ids, assigned_hosts) + ) + + +def _get_obj_id(obj_service, obj_name): + obj = search_by_name(obj_service, obj_name) + if obj is None: + raise Exception("Object '%s' was not found." % obj_name) + return obj.id + + +def main(): + argument_spec = ovirt_full_argument_spec( + state=dict( + choices=['present', 'absent'], + default='present', + ), + cluster=dict(default=None, required=True), + name=dict(default=None, required=True), + description=dict(default=None), + vm_enforcing=dict(default=None, type='bool'), + vm_rule=dict(default=None, choices=['positive', 'negative', 'disabled']), + host_enforcing=dict(default=None, type='bool'), + host_rule=dict(default=None, choices=['positive', 'negative']), + vms=dict(default=None, type='list'), + hosts=dict(default=None, type='list'), + ) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + check_sdk(module) + connection = create_connection(module.params.pop('auth')) + + # Check if unsupported parameters were passed: + supported_41 = ('host_enforcing', 'host_rule', 'hosts') + if not check_support( + version='4.1', + connection=connection, + module=module, + params=supported_41, + ): + module.fail_json( + msg='Following parameters are supported since 4.1: {params}'.format( + params=supported_41, + ) + ) + + try: + clusters_service = connection.system_service().clusters_service() + vms_service = connection.system_service().vms_service() + hosts_service = connection.system_service().hosts_service() + cluster_name = module.params['cluster'] + cluster = search_by_name(clusters_service, cluster_name) + if cluster is None: + raise Exception("Cluster '%s' was not found." % cluster_name) + cluster_service = clusters_service.cluster_service(cluster.id) + affinity_groups_service = cluster_service.affinity_groups_service() + + # Fetch VM ids which should be assigned to affinity group: + vm_ids = sorted([ + _get_obj_id(vms_service, vm_name) + for vm_name in module.params['vms'] + ]) if module.params['vms'] is not None else None + # Fetch host ids which should be assigned to affinity group: + host_ids = sorted([ + _get_obj_id(hosts_service, host_name) + for host_name in module.params['hosts'] + ]) if module.params['hosts'] is not None else None + + affinity_groups_module = AffinityGroupsModule( + connection=connection, + module=module, + service=affinity_groups_service, + vm_ids=vm_ids, + host_ids=host_ids, + ) + + state = module.params['state'] + if state == 'present': + ret = affinity_groups_module.create() + elif state == 'absent': + ret = affinity_groups_module.remove() + + module.exit_json(**ret) + except Exception as e: + module.fail_json(msg=str(e), exception=traceback.format_exc()) + finally: + connection.close(logout=False) + + +if __name__ == "__main__": + main()