openshift_health_check.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. """
  2. Ansible action plugin to execute health checks in OpenShift clusters.
  3. """
  4. # pylint: disable=wrong-import-position,missing-docstring,invalid-name
  5. import sys
  6. import os
  7. from collections import defaultdict
  8. try:
  9. from __main__ import display
  10. except ImportError:
  11. from ansible.utils.display import Display
  12. display = Display()
  13. from ansible.plugins.action import ActionBase
  14. from ansible.module_utils.six import string_types
  15. # Augment sys.path so that we can import checks from a directory relative to
  16. # this callback plugin.
  17. sys.path.insert(1, os.path.dirname(os.path.dirname(__file__)))
  18. from openshift_checks import OpenShiftCheck, OpenShiftCheckException, load_checks # noqa: E402
  19. class ActionModule(ActionBase):
  20. def run(self, tmp=None, task_vars=None):
  21. result = super(ActionModule, self).run(tmp, task_vars)
  22. task_vars = task_vars or {}
  23. # vars are not supportably available in the callback plugin,
  24. # so record any it will need in the result.
  25. result['playbook_context'] = task_vars.get('r_openshift_health_checker_playbook_context')
  26. if "openshift" not in task_vars:
  27. result["failed"] = True
  28. result["msg"] = "'openshift' is undefined, did 'openshift_facts' run?"
  29. return result
  30. try:
  31. known_checks = self.load_known_checks(tmp, task_vars)
  32. args = self._task.args
  33. requested_checks = normalize(args.get('checks', []))
  34. resolved_checks = resolve_checks(requested_checks, known_checks.values())
  35. except OpenShiftCheckException as e:
  36. result["failed"] = True
  37. result["msg"] = str(e)
  38. return result
  39. result["checks"] = check_results = {}
  40. user_disabled_checks = normalize(task_vars.get('openshift_disable_check', []))
  41. for check_name in resolved_checks:
  42. display.banner("CHECK [{} : {}]".format(check_name, task_vars["ansible_host"]))
  43. check = known_checks[check_name]
  44. if not check.is_active():
  45. r = dict(skipped=True, skipped_reason="Not active for this host")
  46. elif check_name in user_disabled_checks:
  47. r = dict(skipped=True, skipped_reason="Disabled by user request")
  48. else:
  49. try:
  50. r = check.run()
  51. except OpenShiftCheckException as e:
  52. r = dict(
  53. failed=True,
  54. msg=str(e),
  55. )
  56. if check.changed:
  57. r["changed"] = True
  58. check_results[check_name] = r
  59. result["changed"] = any(r.get("changed") for r in check_results.values())
  60. if any(r.get("failed") for r in check_results.values()):
  61. result["failed"] = True
  62. result["msg"] = "One or more checks failed"
  63. return result
  64. def load_known_checks(self, tmp, task_vars):
  65. load_checks()
  66. known_checks = {}
  67. for cls in OpenShiftCheck.subclasses():
  68. check_name = cls.name
  69. if check_name in known_checks:
  70. other_cls = known_checks[check_name].__class__
  71. raise OpenShiftCheckException(
  72. "non-unique check name '{}' in: '{}.{}' and '{}.{}'".format(
  73. check_name,
  74. cls.__module__, cls.__name__,
  75. other_cls.__module__, other_cls.__name__))
  76. known_checks[check_name] = cls(execute_module=self._execute_module, tmp=tmp, task_vars=task_vars)
  77. return known_checks
  78. def resolve_checks(names, all_checks):
  79. """Returns a set of resolved check names.
  80. Resolving a check name expands tag references (e.g., "@tag") to all the
  81. checks that contain the given tag. OpenShiftCheckException is raised if
  82. names contains an unknown check or tag name.
  83. names should be a sequence of strings.
  84. all_checks should be a sequence of check classes/instances.
  85. """
  86. known_check_names = set(check.name for check in all_checks)
  87. known_tag_names = set(name for check in all_checks for name in check.tags)
  88. check_names = set(name for name in names if not name.startswith('@'))
  89. tag_names = set(name[1:] for name in names if name.startswith('@'))
  90. unknown_check_names = check_names - known_check_names
  91. unknown_tag_names = tag_names - known_tag_names
  92. if unknown_check_names or unknown_tag_names:
  93. msg = []
  94. if unknown_check_names:
  95. msg.append('Unknown check names: {}.'.format(', '.join(sorted(unknown_check_names))))
  96. if unknown_tag_names:
  97. msg.append('Unknown tag names: {}.'.format(', '.join(sorted(unknown_tag_names))))
  98. msg.append('Make sure there is no typo in the playbook and no files are missing.')
  99. raise OpenShiftCheckException('\n'.join(msg))
  100. tag_to_checks = defaultdict(set)
  101. for check in all_checks:
  102. for tag in check.tags:
  103. tag_to_checks[tag].add(check.name)
  104. resolved = check_names.copy()
  105. for tag in tag_names:
  106. resolved.update(tag_to_checks[tag])
  107. return resolved
  108. def normalize(checks):
  109. """Return a clean list of check names.
  110. The input may be a comma-separated string or a sequence. Leading and
  111. trailing whitespace characters are removed. Empty items are discarded.
  112. """
  113. if isinstance(checks, string_types):
  114. checks = checks.split(',')
  115. return [name.strip() for name in checks if name.strip()]