diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_tags.py b/lib/ansible/modules/cloud/ovirt/ovirt_tags.py
new file mode 100644
index 00000000000..29f4508f9ac
--- /dev/null
+++ b/lib/ansible/modules/cloud/ovirt/ovirt_tags.py
@@ -0,0 +1,213 @@
+#!/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,
+ create_connection,
+ equal,
+ ovirt_full_argument_spec,
+ search_by_name,
+)
+
+
+ANSIBLE_METADATA = {'status': ['preview'],
+ 'supported_by': 'community',
+ 'version': '1.0'}
+
+DOCUMENTATION = '''
+---
+module: ovirt_tags
+short_description: Module to manage tags in oVirt
+version_added: "2.3"
+author: "Ondra Machacek (@machacekondra)"
+description:
+ - "This module manage tags in oVirt. It can also manage assignments
+ of those tags to entities."
+options:
+ name:
+ description:
+ - "Name of the the tag to manage."
+ required: true
+ state:
+ description:
+ - "Should the tag be present or absent."
+ choices: ['present', 'absent']
+ default: present
+ description:
+ description:
+ - "Description of the the tag to manage."
+ parent:
+ description:
+ - "Name of the parent tag."
+ vms:
+ description:
+ - "List of the VMs names, which should have assigned this tag."
+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 tag to vms vm1 and vm2:
+- ovirt_tags:
+ name: mytag
+ vms:
+ - vm1
+ - vm2
+
+# To detach all VMs from tag:
+- ovirt_tags:
+ name: mytag
+ vms: []
+
+# Remove tag
+- ovirt_tags:
+ state: absent
+ name: mytag
+'''
+
+RETURN = '''
+id:
+ description: ID of the tag which is managed
+ returned: On success if tag is found.
+ type: str
+ sample: 7de90f31-222c-436c-a1ca-7e655bd5b60c
+tag:
+ description: "Dictionary of all the tag attributes. Tag attributes can be found on your oVirt instance
+ at following url: https://ovirt.example.com/ovirt-engine/api/model#types/tag."
+ returned: On success if tag is found.
+'''
+
+
+class TagsModule(BaseModule):
+
+ def build_entity(self):
+ return otypes.Tag(
+ name=self._module.params['name'],
+ description=self._module.params['description'],
+ parent=otypes.Tag(
+ name=self._module.params['parent'],
+ ) if self._module.params['parent'] else None,
+ )
+
+ def post_create(self, entity):
+ self.update_check(entity)
+
+ def _update_tag_assignments(self, entity, name):
+ if self._module.params[name] is None:
+ return
+
+ entities_service = getattr(self._connection.system_service(), '%s_service' % name)()
+ current_vms = [
+ vm.name
+ for vm in entities_service.list(search='tag=%s' % self._module.params['name'])
+ ]
+ # Assign tags:
+ for entity_name in self._module.params[name]:
+ entity = search_by_name(entities_service, entity_name)
+ tags_service = entities_service.service(entity.id).tags_service()
+ current_tags = [tag.name for tag in tags_service.list()]
+ # Assign the tag:
+ if self._module.params['name'] not in current_tags:
+ if not self._module.check_mode:
+ tags_service.add(
+ tag=otypes.Tag(
+ name=self._module.params['name'],
+ ),
+ )
+ self.changed = True
+
+ # Unassign tags:
+ for entity_name in [e for e in current_vms if e not in self._module.params[name]]:
+ if not self._module.check_mode:
+ entity = search_by_name(entities_service, entity_name)
+ tags_service = entities_service.service(entity.id).tags_service()
+ tag_id = [tag.id for tag in tags_service.list() if tag.name == self._module.params['name']][0]
+ tags_service.tag_service(tag_id).remove()
+ self.changed = True
+
+ def _get_parent(self, entity):
+ parent = None
+ if entity.parent:
+ parent = self._connection.follow_link(entity.parent).name
+ return parent
+
+ def update_check(self, entity):
+ self._update_tag_assignments(entity, 'vms')
+ self._update_tag_assignments(entity, 'hosts')
+ return (
+ equal(self._module.params.get('description'), entity.description) and
+ equal(self._module.params.get('parent'), self._get_parent(entity))
+ )
+
+
+def main():
+ argument_spec = ovirt_full_argument_spec(
+ state=dict(
+ choices=['present', 'absent'],
+ default='present',
+ ),
+ name=dict(default=None, required=True),
+ description=dict(default=None),
+ parent=dict(default=None),
+ 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)
+
+ try:
+ connection = create_connection(module.params.pop('auth'))
+ tags_service = connection.system_service().tags_service()
+ tags_module = TagsModule(
+ connection=connection,
+ module=module,
+ service=tags_service,
+ )
+
+ state = module.params['state']
+ if state == 'present':
+ ret = tags_module.create()
+ elif state == 'absent':
+ ret = tags_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()
diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_tags_facts.py b/lib/ansible/modules/cloud/ovirt/ovirt_tags_facts.py
new file mode 100644
index 00000000000..78e217906e7
--- /dev/null
+++ b/lib/ansible/modules/cloud/ovirt/ovirt_tags_facts.py
@@ -0,0 +1,154 @@
+#!/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 fnmatch
+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,
+ search_by_name,
+)
+
+
+ANSIBLE_METADATA = {'status': ['preview'],
+ 'supported_by': 'community',
+ 'version': '1.0'}
+
+DOCUMENTATION = '''
+---
+module: ovirt_tags_facts
+short_description: Retrieve facts about one or more oVirt tags
+author: "Ondra Machacek (@machacekondra)"
+version_added: "2.3"
+description:
+ - "Retrieve facts about one or more oVirt tags."
+notes:
+ - "This module creates a new top-level C(ovirt_tags) fact, which
+ contains a list of tags"
+options:
+ name:
+ description:
+ - "Name of the tag which should be listed."
+ vm:
+ description:
+ - "Name of the VM, which tags should be listed."
+ host:
+ description:
+ - "Name of the host, which tags should be listed."
+extends_documentation_fragment: ovirt
+'''
+
+EXAMPLES = '''
+# Examples don't contain auth parameter for simplicity,
+# look at ovirt_auth module to see how to reuse authentication:
+
+# Gather facts about all tags, which names start with C(tag):
+- ovirt_tags_facts:
+ name: tag*
+- debug:
+ var: tags
+
+# Gather facts about all tags, which are assigned to VM C(postgres):
+- ovirt_tags_facts:
+ vm: postgres
+- debug:
+ var: tags
+
+# Gather facts about all tags, which are assigned to host C(west):
+- ovirt_tags_facts:
+ host: west
+- debug:
+ var: tags
+'''
+
+RETURN = '''
+ovirt_tags:
+ description: "List of dictionaries describing the tags. Tags attribues are mapped to dictionary keys,
+ all tags attributes can be found at following url: https://ovirt.example.com/ovirt-engine/api/model#types/tag."
+ returned: On success.
+ type: list
+'''
+
+
+def main():
+ argument_spec = ovirt_facts_full_argument_spec(
+ name=dict(default=None),
+ host=dict(default=None),
+ vm=dict(default=None),
+ )
+ module = AnsibleModule(argument_spec)
+ check_sdk(module)
+
+ try:
+ connection = create_connection(module.params.pop('auth'))
+ tags_service = connection.system_service().tags_service()
+ tags = []
+ all_tags = tags_service.list()
+ if module.params['name']:
+ tags.extend([
+ t for t in all_tags
+ if fnmatch.fnmatch(t.name, module.params['name'])
+ ])
+ if module.params['host']:
+ hosts_service = connection.system_service().hosts_service()
+ host = search_by_name(hosts_service, module.params['host'])
+ if host is None:
+ raise Exception("Host '%s' was not found." % module.params['host'])
+ tags.extend([
+ tag for tag in hosts_service.host_service(host.id).tags_service().list()
+ ])
+ if module.params['vm']:
+ vms_service = connection.system_service().vms_service()
+ vm = search_by_name(vms_service, module.params['vm'])
+ if vm is None:
+ raise Exception("Vm '%s' was not found." % module.params['vm'])
+ tags.extend([
+ tag for tag in vms_service.vm_service(vm.id).tags_service().list()
+ ])
+
+ if not (module.params['vm'] or module.params['host'] or module.params['name']):
+ tags = all_tags
+
+ module.exit_json(
+ changed=False,
+ ansible_facts=dict(
+ ovirt_tags=[
+ get_dict_of_struct(
+ struct=t,
+ connection=connection,
+ fetch_nested=module.params['fetch_nested'],
+ attributes=module.params['nested_attributes'],
+ ) for t in tags
+ ],
+ ),
+ )
+ except Exception as e:
+ module.fail_json(msg=str(e), exception=traceback.format_exc())
+ finally:
+ connection.close(logout=False)
+
+
+if __name__ == '__main__':
+ main()