sanity_checks.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. """
  2. Ansible action plugin to ensure inventory variables are set
  3. appropriately and no conflicting options have been provided.
  4. """
  5. import re
  6. from ansible.plugins.action import ActionBase
  7. from ansible import errors
  8. # Valid values for openshift_deployment_type
  9. VALID_DEPLOYMENT_TYPES = ('origin', 'openshift-enterprise')
  10. # Tuple of variable names and default values if undefined.
  11. NET_PLUGIN_LIST = (('openshift_use_openshift_sdn', True),
  12. ('openshift_use_flannel', False),
  13. ('openshift_use_nuage', False),
  14. ('openshift_use_contiv', False),
  15. ('openshift_use_calico', False))
  16. ENTERPRISE_TAG_REGEX_ERROR = """openshift_image_tag must be in the format
  17. v#.#[.#[.#]]. Examples: v1.2, v3.4.1, v3.5.1.3,
  18. v3.5.1.3.4, v1.2-1, v1.2.3-4, v1.2.3-4.5, v1.2.3-4.5.6
  19. You specified openshift_image_tag={}"""
  20. ORIGIN_TAG_REGEX_ERROR = """openshift_image_tag must be in the format
  21. v#.#.#[-optional.#]. Examples: v1.2.3, v3.5.1-alpha.1
  22. You specified openshift_image_tag={}"""
  23. ORIGIN_TAG_REGEX = {'re': '(^v?\\d+\\.\\d+\\.\\d+(-[\\w\\-\\.]*)?$)',
  24. 'error_msg': ORIGIN_TAG_REGEX_ERROR}
  25. ENTERPRISE_TAG_REGEX = {'re': '(^v\\d+\\.\\d+(\\.\\d+)*(-\\d+(\\.\\d+)*)?$)',
  26. 'error_msg': ENTERPRISE_TAG_REGEX_ERROR}
  27. IMAGE_TAG_REGEX = {'origin': ORIGIN_TAG_REGEX,
  28. 'openshift-enterprise': ENTERPRISE_TAG_REGEX}
  29. UNSUPPORTED_OCP_VERSIONS = {
  30. '^3.8.*$': 'OCP 3.8 is not supported and cannot be installed'
  31. }
  32. CONTAINERIZED_NO_TAG_ERROR_MSG = """To install a containerized Origin release,
  33. you must set openshift_release or openshift_image_tag in your inventory to
  34. specify which version of the OpenShift component images to use.
  35. (Suggestion: add openshift_release="x.y" to inventory.)"""
  36. def to_bool(var_to_check):
  37. """Determine a boolean value given the multiple
  38. ways bools can be specified in ansible."""
  39. # http://yaml.org/type/bool.html
  40. yes_list = (True, 1, "True", "1", "true", "TRUE",
  41. "Yes", "yes", "Y", "y", "YES",
  42. "on", "ON", "On")
  43. return var_to_check in yes_list
  44. class ActionModule(ActionBase):
  45. """Action plugin to execute sanity checks."""
  46. def template_var(self, hostvars, host, varname):
  47. """Retrieve a variable from hostvars and template it.
  48. If undefined, return None type."""
  49. # We will set the current host and variable checked for easy debugging
  50. # if there are any unhandled exceptions.
  51. # pylint: disable=W0201
  52. self.last_checked_var = varname
  53. # pylint: disable=W0201
  54. self.last_checked_host = host
  55. res = hostvars[host].get(varname)
  56. if res is None:
  57. return None
  58. return self._templar.template(res)
  59. def check_openshift_deployment_type(self, hostvars, host):
  60. """Ensure a valid openshift_deployment_type is set"""
  61. openshift_deployment_type = self.template_var(hostvars, host,
  62. 'openshift_deployment_type')
  63. if openshift_deployment_type not in VALID_DEPLOYMENT_TYPES:
  64. type_strings = ", ".join(VALID_DEPLOYMENT_TYPES)
  65. msg = "openshift_deployment_type must be defined and one of {}".format(type_strings)
  66. raise errors.AnsibleModuleError(msg)
  67. return openshift_deployment_type
  68. def check_python_version(self, hostvars, host, distro):
  69. """Ensure python version is 3 for Fedora and python 2 for others"""
  70. ansible_python = self.template_var(hostvars, host, 'ansible_python')
  71. if distro == "Fedora":
  72. if ansible_python['version']['major'] != 3:
  73. msg = "openshift-ansible requires Python 3 for {};".format(distro)
  74. msg += " For information on enabling Python 3 with Ansible,"
  75. msg += " see https://docs.ansible.com/ansible/python_3_support.html"
  76. raise errors.AnsibleModuleError(msg)
  77. else:
  78. if ansible_python['version']['major'] != 2:
  79. msg = "openshift-ansible requires Python 2 for {};".format(distro)
  80. def check_image_tag_format(self, hostvars, host, openshift_deployment_type):
  81. """Ensure openshift_image_tag is formatted correctly"""
  82. openshift_image_tag = self.template_var(hostvars, host, 'openshift_image_tag')
  83. if not openshift_image_tag or openshift_image_tag == 'latest':
  84. return None
  85. regex_to_match = IMAGE_TAG_REGEX[openshift_deployment_type]['re']
  86. res = re.match(regex_to_match, str(openshift_image_tag))
  87. if res is None:
  88. msg = IMAGE_TAG_REGEX[openshift_deployment_type]['error_msg']
  89. msg = msg.format(str(openshift_image_tag))
  90. raise errors.AnsibleModuleError(msg)
  91. def no_origin_image_version(self, hostvars, host, openshift_deployment_type):
  92. """Ensure we can determine what image version to use with origin
  93. fail when:
  94. - openshift_is_containerized
  95. - openshift_deployment_type == 'origin'
  96. - openshift_release is not defined
  97. - openshift_image_tag is not defined"""
  98. if not openshift_deployment_type == 'origin':
  99. return None
  100. oic = self.template_var(hostvars, host, 'openshift_is_containerized')
  101. if not to_bool(oic):
  102. return None
  103. orelease = self.template_var(hostvars, host, 'openshift_release')
  104. oitag = self.template_var(hostvars, host, 'openshift_image_tag')
  105. if not orelease and not oitag:
  106. raise errors.AnsibleModuleError(CONTAINERIZED_NO_TAG_ERROR_MSG)
  107. def network_plugin_check(self, hostvars, host):
  108. """Ensure only one type of network plugin is enabled"""
  109. res = []
  110. # Loop through each possible network plugin boolean, determine the
  111. # actual boolean value, and append results into a list.
  112. for plugin, default_val in NET_PLUGIN_LIST:
  113. res_temp = self.template_var(hostvars, host, plugin)
  114. if res_temp is None:
  115. res_temp = default_val
  116. res.append(to_bool(res_temp))
  117. if sum(res) != 1:
  118. plugin_str = list(zip([x[0] for x in NET_PLUGIN_LIST], res))
  119. msg = "Host Checked: {} Only one of must be true. Found: {}".format(host, plugin_str)
  120. raise errors.AnsibleModuleError(msg)
  121. def check_hostname_vars(self, hostvars, host):
  122. """Checks to ensure openshift_hostname
  123. and openshift_public_hostname
  124. conform to the proper length of 63 characters or less"""
  125. for varname in ('openshift_public_hostname', 'openshift_hostname'):
  126. var_value = self.template_var(hostvars, host, varname)
  127. if var_value and len(var_value) > 63:
  128. msg = '{} must be 63 characters or less'.format(varname)
  129. raise errors.AnsibleModuleError(msg)
  130. def check_supported_ocp_version(self, hostvars, host, openshift_deployment_type):
  131. """Checks that the OCP version supported"""
  132. if openshift_deployment_type == 'origin':
  133. return None
  134. openshift_version = self.template_var(hostvars, host, 'openshift_version')
  135. for regex_to_match, error_msg in UNSUPPORTED_OCP_VERSIONS.items():
  136. res = re.match(regex_to_match, str(openshift_version))
  137. if res is not None:
  138. raise errors.AnsibleModuleError(error_msg)
  139. return None
  140. def run_checks(self, hostvars, host):
  141. """Execute the hostvars validations against host"""
  142. distro = self.template_var(hostvars, host, 'ansible_distribution')
  143. odt = self.check_openshift_deployment_type(hostvars, host)
  144. self.check_python_version(hostvars, host, distro)
  145. self.check_image_tag_format(hostvars, host, odt)
  146. self.no_origin_image_version(hostvars, host, odt)
  147. self.network_plugin_check(hostvars, host)
  148. self.check_hostname_vars(hostvars, host)
  149. self.check_supported_ocp_version(hostvars, host, odt)
  150. def run(self, tmp=None, task_vars=None):
  151. result = super(ActionModule, self).run(tmp, task_vars)
  152. # self.task_vars holds all in-scope variables.
  153. # Ignore settting self.task_vars outside of init.
  154. # pylint: disable=W0201
  155. self.task_vars = task_vars or {}
  156. # pylint: disable=W0201
  157. self.last_checked_host = "none"
  158. # pylint: disable=W0201
  159. self.last_checked_var = "none"
  160. # self._task.args holds task parameters.
  161. # check_hosts is a parameter to this plugin, and should provide
  162. # a list of hosts.
  163. check_hosts = self._task.args.get('check_hosts')
  164. if not check_hosts:
  165. msg = "check_hosts is required"
  166. raise errors.AnsibleModuleError(msg)
  167. # We need to access each host's variables
  168. hostvars = self.task_vars.get('hostvars')
  169. if not hostvars:
  170. msg = hostvars
  171. raise errors.AnsibleModuleError(msg)
  172. # We loop through each host in the provided list check_hosts
  173. for host in check_hosts:
  174. try:
  175. self.run_checks(hostvars, host)
  176. except Exception as uncaught_e:
  177. msg = "last_checked_host: {}, last_checked_var: {};"
  178. msg = msg.format(self.last_checked_host, self.last_checked_var)
  179. msg += str(uncaught_e)
  180. raise errors.AnsibleModuleError(msg)
  181. result["changed"] = False
  182. result["failed"] = False
  183. result["msg"] = "Sanity Checks passed"
  184. return result