openstack.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. #!/usr/bin/env python
  2. # Copyright (c) 2012, Marco Vito Moscaritolo <marco@agavee.com>
  3. # Copyright (c) 2013, Jesse Keating <jesse.keating@rackspace.com>
  4. # Copyright (c) 2015, Hewlett-Packard Development Company, L.P.
  5. # Copyright (c) 2016, Rackspace Australia
  6. #
  7. # This module is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This software is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this software. If not, see <http://www.gnu.org/licenses/>.
  19. # The OpenStack Inventory module uses os-client-config for configuration.
  20. # https://github.com/stackforge/os-client-config
  21. # This means it will either:
  22. # - Respect normal OS_* environment variables like other OpenStack tools
  23. # - Read values from a clouds.yaml file.
  24. # If you want to configure via clouds.yaml, you can put the file in:
  25. # - Current directory
  26. # - ~/.config/openstack/clouds.yaml
  27. # - /etc/openstack/clouds.yaml
  28. # - /etc/ansible/openstack.yml
  29. # The clouds.yaml file can contain entries for multiple clouds and multiple
  30. # regions of those clouds. If it does, this inventory module will connect to
  31. # all of them and present them as one contiguous inventory.
  32. #
  33. # See the adjacent openstack.yml file for an example config file
  34. # There are two ansible inventory specific options that can be set in
  35. # the inventory section.
  36. # expand_hostvars controls whether or not the inventory will make extra API
  37. # calls to fill out additional information about each server
  38. # use_hostnames changes the behavior from registering every host with its UUID
  39. # and making a group of its hostname to only doing this if the
  40. # hostname in question has more than one server
  41. # fail_on_errors causes the inventory to fail and return no hosts if one cloud
  42. # has failed (for example, bad credentials or being offline).
  43. # When set to False, the inventory will return hosts from
  44. # whichever other clouds it can contact. (Default: True)
  45. import argparse
  46. import collections
  47. import os
  48. import sys
  49. import time
  50. from distutils.version import StrictVersion
  51. try:
  52. import json
  53. except:
  54. import simplejson as json
  55. import os_client_config
  56. import shade
  57. import shade.inventory
  58. CONFIG_FILES = ['/etc/ansible/openstack.yaml', '/etc/ansible/openstack.yml']
  59. def get_groups_from_server(server_vars, namegroup=True):
  60. groups = []
  61. region = server_vars['region']
  62. cloud = server_vars['cloud']
  63. metadata = server_vars.get('metadata', {})
  64. # Create a group for the cloud
  65. groups.append(cloud)
  66. # Create a group on region
  67. groups.append(region)
  68. # And one by cloud_region
  69. groups.append("%s_%s" % (cloud, region))
  70. # Check if group metadata key in servers' metadata
  71. if 'group' in metadata:
  72. groups.append(metadata['group'])
  73. for extra_group in metadata.get('groups', '').split(','):
  74. if extra_group:
  75. groups.append(extra_group.strip())
  76. groups.append('instance-%s' % server_vars['id'])
  77. if namegroup:
  78. groups.append(server_vars['name'])
  79. for key in ('flavor', 'image'):
  80. if 'name' in server_vars[key]:
  81. groups.append('%s-%s' % (key, server_vars[key]['name']))
  82. for key, value in iter(metadata.items()):
  83. groups.append('meta-%s_%s' % (key, value))
  84. az = server_vars.get('az', None)
  85. if az:
  86. # Make groups for az, region_az and cloud_region_az
  87. groups.append(az)
  88. groups.append('%s_%s' % (region, az))
  89. groups.append('%s_%s_%s' % (cloud, region, az))
  90. return groups
  91. def get_host_groups(inventory, refresh=False):
  92. (cache_file, cache_expiration_time) = get_cache_settings()
  93. if is_cache_stale(cache_file, cache_expiration_time, refresh=refresh):
  94. groups = to_json(get_host_groups_from_cloud(inventory))
  95. open(cache_file, 'w').write(groups)
  96. else:
  97. groups = open(cache_file, 'r').read()
  98. return groups
  99. def append_hostvars(hostvars, groups, key, server, namegroup=False):
  100. hostvars[key] = dict(
  101. ansible_ssh_host=server['interface_ip'],
  102. openstack=server)
  103. for group in get_groups_from_server(server, namegroup=namegroup):
  104. groups[group].append(key)
  105. def get_host_groups_from_cloud(inventory):
  106. groups = collections.defaultdict(list)
  107. firstpass = collections.defaultdict(list)
  108. hostvars = {}
  109. list_args = {}
  110. if hasattr(inventory, 'extra_config'):
  111. use_hostnames = inventory.extra_config['use_hostnames']
  112. list_args['expand'] = inventory.extra_config['expand_hostvars']
  113. if StrictVersion(shade.__version__) >= StrictVersion("1.6.0"):
  114. list_args['fail_on_cloud_config'] = \
  115. inventory.extra_config['fail_on_errors']
  116. else:
  117. use_hostnames = False
  118. for server in inventory.list_hosts(**list_args):
  119. if 'interface_ip' not in server:
  120. continue
  121. firstpass[server['name']].append(server)
  122. for name, servers in firstpass.items():
  123. if len(servers) == 1 and use_hostnames:
  124. append_hostvars(hostvars, groups, name, servers[0])
  125. else:
  126. server_ids = set()
  127. # Trap for duplicate results
  128. for server in servers:
  129. server_ids.add(server['id'])
  130. if len(server_ids) == 1 and use_hostnames:
  131. append_hostvars(hostvars, groups, name, servers[0])
  132. else:
  133. for server in servers:
  134. append_hostvars(
  135. hostvars, groups, server['id'], server,
  136. namegroup=True)
  137. groups['_meta'] = {'hostvars': hostvars}
  138. return groups
  139. def is_cache_stale(cache_file, cache_expiration_time, refresh=False):
  140. ''' Determines if cache file has expired, or if it is still valid '''
  141. if refresh:
  142. return True
  143. if os.path.isfile(cache_file) and os.path.getsize(cache_file) > 0:
  144. mod_time = os.path.getmtime(cache_file)
  145. current_time = time.time()
  146. if (mod_time + cache_expiration_time) > current_time:
  147. return False
  148. return True
  149. def get_cache_settings():
  150. config = os_client_config.config.OpenStackConfig(
  151. config_files=os_client_config.config.CONFIG_FILES + CONFIG_FILES)
  152. # For inventory-wide caching
  153. cache_expiration_time = config.get_cache_expiration_time()
  154. cache_path = config.get_cache_path()
  155. if not os.path.exists(cache_path):
  156. os.makedirs(cache_path)
  157. cache_file = os.path.join(cache_path, 'ansible-inventory.cache')
  158. return (cache_file, cache_expiration_time)
  159. def to_json(in_dict):
  160. return json.dumps(in_dict, sort_keys=True, indent=2)
  161. def parse_args():
  162. parser = argparse.ArgumentParser(description='OpenStack Inventory Module')
  163. parser.add_argument('--private',
  164. action='store_true',
  165. help='Use private address for ansible host')
  166. parser.add_argument('--refresh', action='store_true',
  167. help='Refresh cached information')
  168. parser.add_argument('--debug', action='store_true', default=False,
  169. help='Enable debug output')
  170. group = parser.add_mutually_exclusive_group(required=True)
  171. group.add_argument('--list', action='store_true',
  172. help='List active servers')
  173. group.add_argument('--host', help='List details about the specific host')
  174. return parser.parse_args()
  175. def main():
  176. args = parse_args()
  177. try:
  178. config_files = os_client_config.config.CONFIG_FILES + CONFIG_FILES
  179. shade.simple_logging(debug=args.debug)
  180. inventory_args = dict(
  181. refresh=args.refresh,
  182. config_files=config_files,
  183. private=args.private,
  184. )
  185. if hasattr(shade.inventory.OpenStackInventory, 'extra_config'):
  186. inventory_args.update(dict(
  187. config_key='ansible',
  188. config_defaults={
  189. 'use_hostnames': False,
  190. 'expand_hostvars': True,
  191. 'fail_on_errors': True,
  192. }
  193. ))
  194. inventory = shade.inventory.OpenStackInventory(**inventory_args)
  195. if args.list:
  196. output = get_host_groups(inventory, refresh=args.refresh)
  197. elif args.host:
  198. output = to_json(inventory.get_host(args.host))
  199. print(output)
  200. except shade.OpenStackCloudException as e:
  201. sys.stderr.write('%s\n' % e.message)
  202. sys.exit(1)
  203. sys.exit(0)
  204. if __name__ == '__main__':
  205. main()