oo_config.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. # TODO: Temporarily disabled due to importing old code into openshift-ansible
  2. # repo. We will work on these over time.
  3. # pylint: disable=bad-continuation,missing-docstring,no-self-use,invalid-name,too-many-instance-attributes,too-few-public-methods
  4. import os
  5. import yaml
  6. from pkg_resources import resource_filename
  7. PERSIST_SETTINGS = [
  8. 'ansible_ssh_user',
  9. 'ansible_config',
  10. 'ansible_log_path',
  11. 'master_routingconfig_subdomain',
  12. 'proxy',
  13. 'proxy_exclude_hosts',
  14. 'variant',
  15. 'variant_version',
  16. 'version',
  17. ]
  18. DEFAULT_REQUIRED_FACTS = ['ip', 'public_ip', 'hostname', 'public_hostname']
  19. PRECONFIGURED_REQUIRED_FACTS = ['hostname', 'public_hostname']
  20. class OOConfigFileError(Exception):
  21. """The provided config file path can't be read/written
  22. """
  23. pass
  24. class OOConfigInvalidHostError(Exception):
  25. """ Host in config is missing both ip and hostname. """
  26. pass
  27. class Host(object):
  28. """ A system we will or have installed OpenShift on. """
  29. def __init__(self, **kwargs):
  30. self.ip = kwargs.get('ip', None)
  31. self.hostname = kwargs.get('hostname', None)
  32. self.public_ip = kwargs.get('public_ip', None)
  33. self.public_hostname = kwargs.get('public_hostname', None)
  34. self.connect_to = kwargs.get('connect_to', None)
  35. self.preconfigured = kwargs.get('preconfigured', None)
  36. self.new_host = kwargs.get('new_host', None)
  37. # Should this host run as an OpenShift master:
  38. self.master = kwargs.get('master', False)
  39. # Should this host run as an OpenShift node:
  40. self.node = kwargs.get('node', False)
  41. # Should this host run as an HAProxy:
  42. self.master_lb = kwargs.get('master_lb', False)
  43. # Should this host run as an HAProxy:
  44. self.storage = kwargs.get('storage', False)
  45. self.containerized = kwargs.get('containerized', False)
  46. if self.connect_to is None:
  47. raise OOConfigInvalidHostError("You must specify either an ip " \
  48. "or hostname as 'connect_to'")
  49. if self.master is False and self.node is False and \
  50. self.master_lb is False and self.storage is False:
  51. raise OOConfigInvalidHostError(
  52. "You must specify each host as either a master or a node.")
  53. def __str__(self):
  54. return self.connect_to
  55. def __repr__(self):
  56. return self.connect_to
  57. def to_dict(self):
  58. """ Used when exporting to yaml. """
  59. d = {}
  60. for prop in ['ip', 'hostname', 'public_ip', 'public_hostname',
  61. 'master', 'node', 'master_lb', 'storage', 'containerized',
  62. 'connect_to', 'preconfigured', 'new_host']:
  63. # If the property is defined (not None or False), export it:
  64. if getattr(self, prop):
  65. d[prop] = getattr(self, prop)
  66. return d
  67. def is_etcd_member(self, all_hosts):
  68. """ Will this host be a member of a standalone etcd cluster. """
  69. if not self.master:
  70. return False
  71. masters = [host for host in all_hosts if host.master]
  72. if len(masters) > 1:
  73. return True
  74. return False
  75. def is_dedicated_node(self):
  76. """ Will this host be a dedicated node. (not a master) """
  77. return self.node and not self.master
  78. def is_schedulable_node(self, all_hosts):
  79. """ Will this host be a node marked as schedulable. """
  80. if not self.node:
  81. return False
  82. if not self.master:
  83. return True
  84. masters = [host for host in all_hosts if host.master]
  85. nodes = [host for host in all_hosts if host.node]
  86. if len(masters) == len(nodes):
  87. return True
  88. return False
  89. class OOConfig(object):
  90. default_dir = os.path.normpath(
  91. os.environ.get('XDG_CONFIG_HOME',
  92. os.environ['HOME'] + '/.config/') + '/openshift/')
  93. default_file = '/installer.cfg.yml'
  94. def __init__(self, config_path):
  95. if config_path:
  96. self.config_path = os.path.normpath(config_path)
  97. else:
  98. self.config_path = os.path.normpath(self.default_dir +
  99. self.default_file)
  100. self.settings = {}
  101. self._read_config()
  102. self._set_defaults()
  103. def _read_config(self):
  104. self.hosts = []
  105. try:
  106. if os.path.exists(self.config_path):
  107. cfgfile = open(self.config_path, 'r')
  108. self.settings = yaml.safe_load(cfgfile.read())
  109. cfgfile.close()
  110. # Use the presence of a Description as an indicator this is
  111. # a legacy config file:
  112. if 'Description' in self.settings:
  113. self._upgrade_legacy_config()
  114. # Parse the hosts into DTO objects:
  115. if 'hosts' in self.settings:
  116. for host in self.settings['hosts']:
  117. self.hosts.append(Host(**host))
  118. # Watchout for the variant_version coming in as a float:
  119. if 'variant_version' in self.settings:
  120. self.settings['variant_version'] = \
  121. str(self.settings['variant_version'])
  122. except IOError, ferr:
  123. raise OOConfigFileError('Cannot open config file "{}": {}'.format(ferr.filename,
  124. ferr.strerror))
  125. except yaml.scanner.ScannerError:
  126. raise OOConfigFileError(
  127. 'Config file "{}" is not a valid YAML document'.format(self.config_path))
  128. def _upgrade_legacy_config(self):
  129. new_hosts = []
  130. remove_settings = ['validated_facts', 'Description', 'Name',
  131. 'Subscription', 'Vendor', 'Version', 'masters', 'nodes']
  132. if 'validated_facts' in self.settings:
  133. for key, value in self.settings['validated_facts'].iteritems():
  134. value['connect_to'] = key
  135. if 'masters' in self.settings and key in self.settings['masters']:
  136. value['master'] = True
  137. if 'nodes' in self.settings and key in self.settings['nodes']:
  138. value['node'] = True
  139. new_hosts.append(value)
  140. self.settings['hosts'] = new_hosts
  141. for s in remove_settings:
  142. if s in self.settings:
  143. del self.settings[s]
  144. # A legacy config implies openshift-enterprise 3.0:
  145. self.settings['variant'] = 'openshift-enterprise'
  146. self.settings['variant_version'] = '3.0'
  147. def _set_defaults(self):
  148. if 'ansible_inventory_directory' not in self.settings:
  149. self.settings['ansible_inventory_directory'] = \
  150. self._default_ansible_inv_dir()
  151. if not os.path.exists(self.settings['ansible_inventory_directory']):
  152. os.makedirs(self.settings['ansible_inventory_directory'])
  153. if 'ansible_plugins_directory' not in self.settings:
  154. self.settings['ansible_plugins_directory'] = \
  155. resource_filename(__name__, 'ansible_plugins')
  156. if 'version' not in self.settings:
  157. self.settings['version'] = 'v1'
  158. if 'ansible_callback_facts_yaml' not in self.settings:
  159. self.settings['ansible_callback_facts_yaml'] = '%s/callback_facts.yaml' % \
  160. self.settings['ansible_inventory_directory']
  161. if 'ansible_ssh_user' not in self.settings:
  162. self.settings['ansible_ssh_user'] = ''
  163. self.settings['ansible_inventory_path'] = \
  164. '{}/hosts'.format(os.path.dirname(self.config_path))
  165. # clean up any empty sets
  166. for setting in self.settings.keys():
  167. if not self.settings[setting]:
  168. self.settings.pop(setting)
  169. def _default_ansible_inv_dir(self):
  170. return os.path.normpath(
  171. os.path.dirname(self.config_path) + "/.ansible")
  172. def calc_missing_facts(self):
  173. """
  174. Determine which host facts are not defined in the config.
  175. Returns a hash of host to a list of the missing facts.
  176. """
  177. result = {}
  178. for host in self.hosts:
  179. missing_facts = []
  180. if host.preconfigured:
  181. required_facts = PRECONFIGURED_REQUIRED_FACTS
  182. else:
  183. required_facts = DEFAULT_REQUIRED_FACTS
  184. for required_fact in required_facts:
  185. if not getattr(host, required_fact):
  186. missing_facts.append(required_fact)
  187. if len(missing_facts) > 0:
  188. result[host.connect_to] = missing_facts
  189. return result
  190. def save_to_disk(self):
  191. out_file = open(self.config_path, 'w')
  192. out_file.write(self.yaml())
  193. out_file.close()
  194. def persist_settings(self):
  195. p_settings = {}
  196. for setting in PERSIST_SETTINGS:
  197. if setting in self.settings and self.settings[setting]:
  198. p_settings[setting] = self.settings[setting]
  199. p_settings['hosts'] = []
  200. for host in self.hosts:
  201. p_settings['hosts'].append(host.to_dict())
  202. if self.settings['ansible_inventory_directory'] != \
  203. self._default_ansible_inv_dir():
  204. p_settings['ansible_inventory_directory'] = \
  205. self.settings['ansible_inventory_directory']
  206. return p_settings
  207. def yaml(self):
  208. return yaml.safe_dump(self.persist_settings(), default_flow_style=False)
  209. def __str__(self):
  210. return self.yaml()
  211. def get_host(self, name):
  212. for host in self.hosts:
  213. if host.connect_to == name:
  214. return host
  215. return None