resources.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. #!/usr/bin/env python
  2. """
  3. This library is used by the OpenStack's dynamic inventories.
  4. It produces the inventory in a Python dict structure based on the current
  5. environment.
  6. """
  7. from __future__ import print_function
  8. import argparse
  9. import json
  10. import os
  11. try:
  12. import ConfigParser
  13. except ImportError:
  14. import configparser as ConfigParser
  15. from keystoneauth1.exceptions.catalog import EndpointNotFound
  16. import shade
  17. OPENSHIFT_CLUSTER = os.getenv('OPENSHIFT_CLUSTER')
  18. def base_openshift_inventory(cluster_hosts):
  19. '''Set the base openshift inventory.'''
  20. inventory = {}
  21. masters = [server.name for server in cluster_hosts
  22. if server.metadata['host-type'] == 'master']
  23. etcd = [server.name for server in cluster_hosts
  24. if server.metadata['host-type'] == 'etcd']
  25. if not etcd:
  26. etcd = masters
  27. infra_hosts = [server.name for server in cluster_hosts
  28. if server.metadata['host-type'] == 'node' and
  29. server.metadata['sub-host-type'] == 'infra']
  30. app = [server.name for server in cluster_hosts
  31. if server.metadata['host-type'] == 'node' and
  32. server.metadata['sub-host-type'] == 'app']
  33. cns = [server.name for server in cluster_hosts
  34. if server.metadata['host-type'] == 'cns']
  35. load_balancers = [server.name for server in cluster_hosts
  36. if server.metadata['host-type'] == 'lb']
  37. # NOTE: everything that should go to the `[nodes]` group:
  38. nodes = list(set(masters + etcd + infra_hosts + app + cns))
  39. # NOTE: all OpenShift nodes, including `[lb]`, `[nfs]`, etc.:
  40. osev3 = list(set(nodes + load_balancers))
  41. inventory['OSEv3'] = {'hosts': osev3, 'vars': {}}
  42. inventory['openstack_nodes'] = {'hosts': nodes}
  43. inventory['openstack_master_nodes'] = {'hosts': masters}
  44. inventory['openstack_etcd_nodes'] = {'hosts': etcd}
  45. inventory['openstack_infra_nodes'] = {'hosts': infra_hosts}
  46. inventory['openstack_compute_nodes'] = {'hosts': app}
  47. inventory['openstack_cns_nodes'] = {'hosts': cns}
  48. inventory['lb'] = {'hosts': load_balancers}
  49. inventory['localhost'] = {'ansible_connection': 'local'}
  50. return inventory
  51. def get_docker_storage_mountpoints(volumes):
  52. '''Check volumes to see if they're being used for docker storage'''
  53. docker_storage_mountpoints = {}
  54. for volume in volumes:
  55. if volume.metadata.get('purpose') == "openshift_docker_storage":
  56. for attachment in volume.attachments:
  57. if attachment.server_id in docker_storage_mountpoints:
  58. docker_storage_mountpoints[attachment.server_id].append(attachment.device)
  59. else:
  60. docker_storage_mountpoints[attachment.server_id] = [attachment.device]
  61. return docker_storage_mountpoints
  62. def _get_hostvars(server, docker_storage_mountpoints):
  63. ssh_ip_address = server.public_v4 or server.private_v4
  64. hostvars = {
  65. 'ansible_host': ssh_ip_address
  66. }
  67. public_v4 = server.public_v4 or server.private_v4
  68. private_v4 = server.private_v4 or server.public_v4
  69. if public_v4:
  70. hostvars['public_v4'] = public_v4
  71. hostvars['openshift_public_ip'] = public_v4
  72. # TODO(shadower): what about multiple networks?
  73. if private_v4:
  74. hostvars['private_v4'] = private_v4
  75. hostvars['openshift_ip'] = private_v4
  76. # NOTE(shadower): Yes, we set both hostname and IP to the private
  77. # IP address for each node. OpenStack doesn't resolve nodes by
  78. # name at all, so using a hostname here would require an internal
  79. # DNS which would complicate the setup and potentially introduce
  80. # performance issues.
  81. hostvars['openshift_kubelet_name_override'] = server.metadata.get(
  82. 'openshift_kubelet_name_override', private_v4)
  83. hostvars['openshift_public_hostname'] = server.name
  84. if server.metadata['host-type'] == 'cns':
  85. hostvars['glusterfs_devices'] = ['/dev/nvme0n1']
  86. group_name = server.metadata.get('openshift_node_group_name')
  87. hostvars['openshift_node_group_name'] = group_name
  88. # check for attached docker storage volumes
  89. if 'os-extended-volumes:volumes_attached' in server:
  90. if server.id in docker_storage_mountpoints:
  91. hostvars['docker_storage_mountpoints'] = ' '.join(
  92. docker_storage_mountpoints[server.id])
  93. return hostvars
  94. def build_inventory():
  95. '''Build the dynamic inventory.'''
  96. cloud = shade.openstack_cloud()
  97. # Use an environment variable to optionally skip returning the app nodes.
  98. show_compute_nodes = os.environ.get('OPENSTACK_SHOW_COMPUTE_NODES', 'true').lower() == "true"
  99. # If `OPENSHIFT_CLUSTER` env variable is defined then it's used to
  100. # filter servers by metadata.clusterid attribute value.
  101. cluster_hosts = [
  102. server for server in cloud.list_servers()
  103. if 'clusterid' in server.get('metadata', []) and
  104. (OPENSHIFT_CLUSTER is None or server.metadata.clusterid == OPENSHIFT_CLUSTER) and
  105. (show_compute_nodes or server.metadata.get('sub-host-type') != 'app')]
  106. inventory = base_openshift_inventory(cluster_hosts)
  107. inventory['_meta'] = {'hostvars': {}}
  108. # Some clouds don't have Cinder. That's okay:
  109. try:
  110. volumes = cloud.list_volumes()
  111. except EndpointNotFound:
  112. volumes = []
  113. # cinder volumes used for docker storage
  114. docker_storage_mountpoints = get_docker_storage_mountpoints(volumes)
  115. for server in cluster_hosts:
  116. inventory['_meta']['hostvars'][server.name] = _get_hostvars(
  117. server,
  118. docker_storage_mountpoints)
  119. stout = _get_stack_outputs(cloud)
  120. if stout is not None:
  121. try:
  122. inventory['localhost'].update({
  123. 'openshift_openstack_api_lb_provider':
  124. stout['api_lb_provider'],
  125. 'openshift_openstack_api_lb_port_id':
  126. stout['api_lb_vip_port_id'],
  127. 'openshift_openstack_api_lb_sg_id':
  128. stout['api_lb_sg_id']})
  129. except KeyError:
  130. pass # Not an API load balanced deployment
  131. try:
  132. inventory['OSEv3']['vars'][
  133. 'openshift_master_cluster_hostname'] = stout['private_api_ip']
  134. except KeyError:
  135. pass # Internal LB not specified
  136. inventory['localhost']['openshift_openstack_private_api_ip'] = \
  137. stout.get('private_api_ip')
  138. inventory['localhost']['openshift_openstack_public_api_ip'] = \
  139. stout.get('public_api_ip')
  140. inventory['localhost']['openshift_openstack_public_router_ip'] = \
  141. stout.get('public_router_ip')
  142. try:
  143. inventory['OSEv3']['vars'] = _get_kuryr_vars(cloud, stout)
  144. except KeyError:
  145. pass # Not a kuryr deployment
  146. return inventory
  147. def _get_stack_outputs(cloud_client):
  148. """Returns a dictionary with the stack outputs"""
  149. cluster_name = OPENSHIFT_CLUSTER or 'openshift-cluster'
  150. stack = cloud_client.get_stack(cluster_name)
  151. if stack is None or stack['stack_status'] not in (
  152. 'CREATE_COMPLETE', 'UPDATE_COMPLETE'):
  153. return None
  154. data = {}
  155. for output in stack['outputs']:
  156. data[output['output_key']] = output['output_value']
  157. return data
  158. def _get_kuryr_vars(cloud_client, data):
  159. """Returns a dictionary of Kuryr variables resulting of heat stacking"""
  160. settings = {}
  161. settings['kuryr_openstack_pod_subnet_id'] = data['pod_subnet']
  162. if 'pod_subnet_pool' in data:
  163. settings['kuryr_openstack_pod_subnet_pool_id'] = data[
  164. 'pod_subnet_pool']
  165. if 'sg_allow_from_default' in data:
  166. settings['kuryr_openstack_sg_allow_from_default_id'] = data[
  167. 'sg_allow_from_default']
  168. if 'sg_allow_from_namespace' in data:
  169. settings['kuryr_openstack_sg_allow_from_namespace_id'] = data[
  170. 'sg_allow_from_namespace']
  171. settings['kuryr_openstack_pod_router_id'] = data['pod_router']
  172. settings['kuryr_openstack_worker_nodes_subnet_id'] = data['vm_subnet']
  173. settings['kuryr_openstack_service_subnet_id'] = data['service_subnet']
  174. settings['kuryr_openstack_pod_sg_id'] = data['pod_access_sg_id']
  175. settings['kuryr_openstack_pod_project_id'] = (
  176. cloud_client.current_project_id)
  177. settings['kuryr_openstack_api_lb_ip'] = data['private_api_ip']
  178. settings['kuryr_openstack_auth_url'] = cloud_client.auth['auth_url']
  179. settings['kuryr_openstack_username'] = cloud_client.auth['username']
  180. settings['kuryr_openstack_password'] = cloud_client.auth['password']
  181. if 'user_domain_id' in cloud_client.auth:
  182. settings['kuryr_openstack_user_domain_name'] = (
  183. cloud_client.auth['user_domain_id'])
  184. else:
  185. settings['kuryr_openstack_user_domain_name'] = (
  186. cloud_client.auth['user_domain_name'])
  187. # FIXME(apuimedo): consolidate kuryr controller credentials into the same
  188. # vars the openstack playbook uses.
  189. settings['kuryr_openstack_project_id'] = cloud_client.current_project_id
  190. if 'project_domain_id' in cloud_client.auth:
  191. settings['kuryr_openstack_project_domain_name'] = (
  192. cloud_client.auth['project_domain_id'])
  193. else:
  194. settings['kuryr_openstack_project_domain_name'] = (
  195. cloud_client.auth['project_domain_name'])
  196. return settings
  197. def output_inventory(inventory, output_file):
  198. """Outputs inventory into a file in ini format"""
  199. config = ConfigParser.ConfigParser(allow_no_value=True)
  200. host_meta_vars = _get_host_meta_vars_as_dict(inventory)
  201. for key in sorted(inventory.keys()):
  202. if key == 'localhost':
  203. config.add_section('localhost')
  204. config.set('localhost', 'localhost')
  205. config.add_section('localhost:vars')
  206. for var, value in inventory['localhost'].items():
  207. config.set('localhost:vars', var, value)
  208. elif key not in ('localhost', '_meta'):
  209. if 'hosts' in inventory[key]:
  210. config.add_section(key)
  211. for host in inventory[key]['hosts']:
  212. if host in host_meta_vars.keys():
  213. config.set(key, host + " " + host_meta_vars[host])
  214. else:
  215. config.set(key, host)
  216. if 'vars' in inventory[key]:
  217. config.add_section(key + ":vars")
  218. for var, value in inventory[key]['vars'].items():
  219. config.set(key + ":vars", var, value)
  220. with open(output_file, 'w') as configfile:
  221. config.write(configfile)
  222. def _get_host_meta_vars_as_dict(inventory):
  223. """parse host meta vars from inventory as dict"""
  224. host_meta_vars = {}
  225. if '_meta' in inventory.keys():
  226. if 'hostvars' in inventory['_meta']:
  227. for host in inventory['_meta']['hostvars'].keys():
  228. host_meta_vars[host] = ' '.join(
  229. '{}={}'.format(key, val) for key, val in inventory['_meta']['hostvars'][host].items())
  230. return host_meta_vars
  231. def parse_args():
  232. """parse arguments to script"""
  233. parser = argparse.ArgumentParser(description="Create ansible inventory.")
  234. parser.add_argument('--static', type=str, default='',
  235. help='File to store a static inventory in.')
  236. parser.add_argument('--list', action="store_true", default=False,
  237. help='List inventory.')
  238. return parser.parse_args()
  239. def main(inventory_builder):
  240. """Ansible dynamic inventory entry point."""
  241. if parse_args().static:
  242. output_inventory(inventory_builder(), parse_args().static)
  243. else:
  244. print(json.dumps(inventory_builder(), indent=4, sort_keys=True))