libvirt_generic.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. #!/usr/bin/env python2
  2. '''
  3. libvirt external inventory script
  4. =================================
  5. Ansible has a feature where instead of reading from /etc/ansible/hosts
  6. as a text file, it can query external programs to obtain the list
  7. of hosts, groups the hosts are in, and even variables to assign to each host.
  8. To use this, copy this file over /etc/ansible/hosts and chmod +x the file.
  9. This, more or less, allows you to keep one central database containing
  10. info about all of your managed instances.
  11. '''
  12. # (c) 2015, Jason DeTiberus <jdetiber@redhat.com>
  13. #
  14. # This file is part of Ansible,
  15. #
  16. # Ansible is free software: you can redistribute it and/or modify
  17. # it under the terms of the GNU General Public License as published by
  18. # the Free Software Foundation, either version 3 of the License, or
  19. # (at your option) any later version.
  20. #
  21. # Ansible is distributed in the hope that it will be useful,
  22. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  24. # GNU General Public License for more details.
  25. #
  26. # You should have received a copy of the GNU General Public License
  27. # along with Ansible. If not, see <http://www.gnu.org/licenses/>.
  28. ######################################################################
  29. import argparse
  30. import ConfigParser
  31. import os
  32. import sys
  33. import libvirt
  34. import xml.etree.ElementTree as ET
  35. try:
  36. import json
  37. except ImportError:
  38. import simplejson as json
  39. class LibvirtInventory(object):
  40. ''' libvirt dynamic inventory '''
  41. def __init__(self):
  42. ''' Main execution path '''
  43. self.inventory = dict() # A list of groups and the hosts in that group
  44. self.cache = dict() # Details about hosts in the inventory
  45. # Read settings and parse CLI arguments
  46. self.read_settings()
  47. self.parse_cli_args()
  48. if self.args.host:
  49. print _json_format_dict(self.get_host_info(), self.args.pretty)
  50. elif self.args.list:
  51. print _json_format_dict(self.get_inventory(), self.args.pretty)
  52. else: # default action with no options
  53. print _json_format_dict(self.get_inventory(), self.args.pretty)
  54. def read_settings(self):
  55. ''' Reads the settings from the libvirt.ini file '''
  56. config = ConfigParser.SafeConfigParser()
  57. config.read(
  58. os.path.dirname(os.path.realpath(__file__)) + '/libvirt.ini'
  59. )
  60. self.libvirt_uri = config.get('libvirt', 'uri')
  61. def parse_cli_args(self):
  62. ''' Command line argument processing '''
  63. parser = argparse.ArgumentParser(
  64. description='Produce an Ansible Inventory file based on libvirt'
  65. )
  66. parser.add_argument(
  67. '--list',
  68. action='store_true',
  69. default=True,
  70. help='List instances (default: True)'
  71. )
  72. parser.add_argument(
  73. '--host',
  74. action='store',
  75. help='Get all the variables about a specific instance'
  76. )
  77. parser.add_argument(
  78. '--pretty',
  79. action='store_true',
  80. default=False,
  81. help='Pretty format (default: False)'
  82. )
  83. self.args = parser.parse_args()
  84. def get_host_info(self):
  85. ''' Get variables about a specific host '''
  86. inventory = self.get_inventory()
  87. if self.args.host in inventory['_meta']['hostvars']:
  88. return inventory['_meta']['hostvars'][self.args.host]
  89. def get_inventory(self):
  90. ''' Construct the inventory '''
  91. inventory = dict(_meta=dict(hostvars=dict()))
  92. conn = libvirt.openReadOnly(self.libvirt_uri)
  93. if conn is None:
  94. print "Failed to open connection to %s" % self.libvirt_uri
  95. sys.exit(1)
  96. domains = conn.listAllDomains()
  97. if domains is None:
  98. print "Failed to list domains for connection %s" % self.libvirt_uri
  99. sys.exit(1)
  100. for domain in domains:
  101. hostvars = dict(libvirt_name=domain.name(),
  102. libvirt_id=domain.ID(),
  103. libvirt_uuid=domain.UUIDString())
  104. domain_name = domain.name()
  105. # TODO: add support for guests that are not in a running state
  106. state, _ = domain.state()
  107. # 2 is the state for a running guest
  108. if state != 1:
  109. continue
  110. hostvars['libvirt_status'] = 'running'
  111. root = ET.fromstring(domain.XMLDesc())
  112. ansible_ns = {'ansible': 'https://github.com/ansible/ansible'}
  113. for tag_elem in root.findall('./metadata/ansible:tags/ansible:tag', ansible_ns):
  114. tag = tag_elem.text
  115. _push(inventory, "tag_%s" % tag, domain_name)
  116. _push(hostvars, 'libvirt_tags', tag)
  117. # TODO: support more than one network interface, also support
  118. # interface types other than 'network'
  119. interface = root.find("./devices/interface[@type='network']")
  120. if interface is not None:
  121. source_elem = interface.find('source')
  122. mac_elem = interface.find('mac')
  123. if source_elem is not None and \
  124. mac_elem is not None:
  125. # Adding this to disable pylint check specifically
  126. # ignoring libvirt-python versions that
  127. # do not include DHCPLeases
  128. # This is needed until we upgrade the build bot to
  129. # RHEL7 (>= 1.2.6 libvirt)
  130. # pylint: disable=no-member
  131. dhcp_leases = conn.networkLookupByName(source_elem.get('network')) \
  132. .DHCPLeases(mac_elem.get('address'))
  133. if len(dhcp_leases) > 0:
  134. ip_address = dhcp_leases[0]['ipaddr']
  135. hostvars['ansible_ssh_host'] = ip_address
  136. hostvars['libvirt_ip_address'] = ip_address
  137. inventory['_meta']['hostvars'][domain_name] = hostvars
  138. return inventory
  139. def _push(my_dict, key, element):
  140. '''
  141. Push element to the my_dict[key] list.
  142. After having initialized my_dict[key] if it dosn't exist.
  143. '''
  144. if key in my_dict:
  145. my_dict[key].append(element)
  146. else:
  147. my_dict[key] = [element]
  148. def _json_format_dict(data, pretty=False):
  149. ''' Serialize data to a JSON formated str '''
  150. if pretty:
  151. return json.dumps(data, sort_keys=True, indent=2)
  152. else:
  153. return json.dumps(data)
  154. LibvirtInventory()