1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3# 4# Copyright (c) 2016 Red Hat, Inc. 5# 6# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 7 8from __future__ import (absolute_import, division, print_function) 9__metaclass__ = type 10 11 12""" 13oVirt dynamic inventory script 14================================= 15 16Generates dynamic inventory file for oVirt. 17 18Script will return following attributes for each virtual machine: 19 - id 20 - name 21 - host 22 - cluster 23 - status 24 - description 25 - fqdn 26 - os_type 27 - template 28 - tags 29 - statistics 30 - devices 31 32When run in --list mode, virtual machines are grouped by the following categories: 33 - cluster 34 - tag 35 - status 36 37 Note: If there is some virtual machine which has has more tags it will be in both tag 38 records. 39 40Examples: 41 # Execute update of system on webserver virtual machine: 42 43 $ ansible -i contrib/inventory/ovirt4.py webserver -m yum -a "name=* state=latest" 44 45 # Get webserver virtual machine information: 46 47 $ contrib/inventory/ovirt4.py --host webserver 48 49Author: Ondra Machacek (@machacekondra) 50""" 51 52import argparse 53import os 54import sys 55 56from collections import defaultdict 57 58from ansible.module_utils.six.moves import configparser 59from ansible.module_utils.six import PY2 60 61import json 62 63try: 64 import ovirtsdk4 as sdk 65 import ovirtsdk4.types as otypes 66except ImportError: 67 print('oVirt inventory script requires ovirt-engine-sdk-python >= 4.0.0') 68 sys.exit(1) 69 70 71def parse_args(): 72 """ 73 Create command line parser for oVirt dynamic inventory script. 74 """ 75 parser = argparse.ArgumentParser( 76 description='Ansible dynamic inventory script for oVirt.', 77 ) 78 parser.add_argument( 79 '--list', 80 action='store_true', 81 default=True, 82 help='Get data of all virtual machines (default: True).', 83 ) 84 parser.add_argument( 85 '--host', 86 help='Get data of virtual machines running on specified host.', 87 ) 88 parser.add_argument( 89 '--pretty', 90 action='store_true', 91 default=False, 92 help='Pretty format (default: False).', 93 ) 94 return parser.parse_args() 95 96 97def create_connection(): 98 """ 99 Create a connection to oVirt engine API. 100 """ 101 # Get the path of the configuration file, by default use 102 # 'ovirt.ini' file in script directory: 103 default_path = os.path.join( 104 os.path.dirname(os.path.realpath(__file__)), 105 'ovirt.ini', 106 ) 107 config_path = os.environ.get('OVIRT_INI_PATH', default_path) 108 109 # Create parser and add ovirt section if it doesn't exist: 110 if PY2: 111 config = configparser.SafeConfigParser( 112 defaults={ 113 'ovirt_url': os.environ.get('OVIRT_URL'), 114 'ovirt_username': os.environ.get('OVIRT_USERNAME'), 115 'ovirt_password': os.environ.get('OVIRT_PASSWORD'), 116 'ovirt_ca_file': os.environ.get('OVIRT_CAFILE', ''), 117 }, allow_no_value=True 118 ) 119 else: 120 config = configparser.ConfigParser( 121 defaults={ 122 'ovirt_url': os.environ.get('OVIRT_URL'), 123 'ovirt_username': os.environ.get('OVIRT_USERNAME'), 124 'ovirt_password': os.environ.get('OVIRT_PASSWORD'), 125 'ovirt_ca_file': os.environ.get('OVIRT_CAFILE', ''), 126 }, allow_no_value=True 127 ) 128 if not config.has_section('ovirt'): 129 config.add_section('ovirt') 130 config.read(config_path) 131 132 # Create a connection with options defined in ini file: 133 return sdk.Connection( 134 url=config.get('ovirt', 'ovirt_url'), 135 username=config.get('ovirt', 'ovirt_username'), 136 password=config.get('ovirt', 'ovirt_password', raw=True), 137 ca_file=config.get('ovirt', 'ovirt_ca_file') or None, 138 insecure=not config.get('ovirt', 'ovirt_ca_file'), 139 ) 140 141 142def get_dict_of_struct(connection, vm): 143 """ 144 Transform SDK Vm Struct type to Python dictionary. 145 """ 146 if vm is None: 147 return dict() 148 149 vms_service = connection.system_service().vms_service() 150 clusters_service = connection.system_service().clusters_service() 151 vm_service = vms_service.vm_service(vm.id) 152 devices = vm_service.reported_devices_service().list() 153 tags = vm_service.tags_service().list() 154 stats = vm_service.statistics_service().list() 155 labels = vm_service.affinity_labels_service().list() 156 groups = clusters_service.cluster_service( 157 vm.cluster.id 158 ).affinity_groups_service().list() 159 160 return { 161 'id': vm.id, 162 'name': vm.name, 163 'host': connection.follow_link(vm.host).name if vm.host else None, 164 'cluster': connection.follow_link(vm.cluster).name, 165 'status': str(vm.status), 166 'description': vm.description, 167 'fqdn': vm.fqdn, 168 'os_type': vm.os.type, 169 'template': connection.follow_link(vm.template).name, 170 'tags': [tag.name for tag in tags], 171 'affinity_labels': [label.name for label in labels], 172 'affinity_groups': [ 173 group.name for group in groups 174 if vm.name in [vm.name for vm in connection.follow_link(group.vms)] 175 ], 176 'statistics': dict( 177 (stat.name, stat.values[0].datum) for stat in stats if stat.values 178 ), 179 'devices': dict( 180 (device.name, [ip.address for ip in device.ips]) for device in devices if device.ips 181 ), 182 'ansible_host': next((device.ips[0].address for device in devices if device.ips), None) 183 } 184 185 186def get_data(connection, vm_name=None): 187 """ 188 Obtain data of `vm_name` if specified, otherwise obtain data of all vms. 189 """ 190 vms_service = connection.system_service().vms_service() 191 clusters_service = connection.system_service().clusters_service() 192 193 if vm_name: 194 vm = vms_service.list(search='name=%s' % vm_name) or [None] 195 data = get_dict_of_struct( 196 connection=connection, 197 vm=vm[0], 198 ) 199 else: 200 vms = dict() 201 data = defaultdict(list) 202 for vm in vms_service.list(): 203 name = vm.name 204 vm_service = vms_service.vm_service(vm.id) 205 cluster_service = clusters_service.cluster_service(vm.cluster.id) 206 207 # Add vm to vms dict: 208 vms[name] = get_dict_of_struct(connection, vm) 209 210 # Add vm to cluster group: 211 cluster_name = connection.follow_link(vm.cluster).name 212 data['cluster_%s' % cluster_name].append(name) 213 214 # Add vm to tag group: 215 tags_service = vm_service.tags_service() 216 for tag in tags_service.list(): 217 data['tag_%s' % tag.name].append(name) 218 219 # Add vm to status group: 220 data['status_%s' % vm.status].append(name) 221 222 # Add vm to affinity group: 223 for group in cluster_service.affinity_groups_service().list(): 224 if vm.name in [ 225 v.name for v in connection.follow_link(group.vms) 226 ]: 227 data['affinity_group_%s' % group.name].append(vm.name) 228 229 # Add vm to affinity label group: 230 affinity_labels_service = vm_service.affinity_labels_service() 231 for label in affinity_labels_service.list(): 232 data['affinity_label_%s' % label.name].append(name) 233 234 data["_meta"] = { 235 'hostvars': vms, 236 } 237 238 return data 239 240 241def main(): 242 args = parse_args() 243 connection = create_connection() 244 245 print( 246 json.dumps( 247 obj=get_data( 248 connection=connection, 249 vm_name=args.host, 250 ), 251 sort_keys=args.pretty, 252 indent=args.pretty * 2, 253 ) 254 ) 255 256 257if __name__ == '__main__': 258 main() 259