__init__.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. """
  2. Health checks for OpenShift clusters.
  3. """
  4. import operator
  5. import os
  6. from abc import ABCMeta, abstractmethod, abstractproperty
  7. from importlib import import_module
  8. from ansible.module_utils import six
  9. from ansible.module_utils.six.moves import reduce # pylint: disable=import-error,redefined-builtin
  10. class OpenShiftCheckException(Exception):
  11. """Raised when a check cannot proceed."""
  12. pass
  13. @six.add_metaclass(ABCMeta)
  14. class OpenShiftCheck(object):
  15. """
  16. A base class for defining checks for an OpenShift cluster environment.
  17. Expect optional params: method execute_module, dict task_vars, and string tmp.
  18. execute_module is expected to have a signature compatible with _execute_module
  19. from ansible plugins/action/__init__.py, e.g.:
  20. def execute_module(module_name=None, module_args=None, tmp=None, task_vars=None, *args):
  21. This is stored so that it can be invoked in subclasses via check.execute_module("name", args)
  22. which provides the check's stored task_vars and tmp.
  23. """
  24. def __init__(self, execute_module=None, task_vars=None, tmp=None):
  25. self._execute_module = execute_module
  26. self.task_vars = task_vars or {}
  27. self.tmp = tmp
  28. @abstractproperty
  29. def name(self):
  30. """The name of this check, usually derived from the class name."""
  31. return "openshift_check"
  32. @property
  33. def tags(self):
  34. """A list of tags that this check satisfy.
  35. Tags are used to reference multiple checks with a single '@tagname'
  36. special check name.
  37. """
  38. return []
  39. @staticmethod
  40. def is_active():
  41. """Returns true if this check applies to the ansible-playbook run."""
  42. return True
  43. @abstractmethod
  44. def run(self):
  45. """Executes a check, normally implemented as a module."""
  46. return {}
  47. @classmethod
  48. def subclasses(cls):
  49. """Returns a generator of subclasses of this class and its subclasses."""
  50. # AUDIT: no-member makes sense due to this having a metaclass
  51. for subclass in cls.__subclasses__(): # pylint: disable=no-member
  52. yield subclass
  53. for subclass in subclass.subclasses():
  54. yield subclass
  55. def execute_module(self, module_name=None, module_args=None):
  56. """Invoke an Ansible module from a check.
  57. Invoke stored _execute_module, normally copied from the action
  58. plugin, with its params and the task_vars and tmp given at
  59. check initialization. No positional parameters beyond these
  60. are specified. If it's necessary to specify any of the other
  61. parameters to _execute_module then that should just be invoked
  62. directly (with awareness of changes in method signature per
  63. Ansible version).
  64. So e.g. check.execute_module("foo", dict(arg1=...))
  65. Return: result hash from module execution.
  66. """
  67. if self._execute_module is None:
  68. raise NotImplementedError(
  69. self.__class__.__name__ +
  70. " invoked execute_module without providing the method at initialization."
  71. )
  72. return self._execute_module(module_name, module_args, self.tmp, self.task_vars)
  73. def get_var(self, *keys, **kwargs):
  74. """Get deeply nested values from task_vars.
  75. Ansible task_vars structures are Python dicts, often mapping strings to
  76. other dicts. This helper makes it easier to get a nested value, raising
  77. OpenShiftCheckException when a key is not found or returning a default value
  78. provided as a keyword argument.
  79. """
  80. try:
  81. value = reduce(operator.getitem, keys, self.task_vars)
  82. except (KeyError, TypeError):
  83. if "default" in kwargs:
  84. return kwargs["default"]
  85. raise OpenShiftCheckException("'{}' is undefined".format(".".join(map(str, keys))))
  86. return value
  87. @staticmethod
  88. def get_major_minor_version(openshift_image_tag):
  89. """Parse and return the deployed version of OpenShift as a tuple."""
  90. if openshift_image_tag and openshift_image_tag[0] == 'v':
  91. openshift_image_tag = openshift_image_tag[1:]
  92. # map major release versions across releases
  93. # to a common major version
  94. openshift_major_release_version = {
  95. "1": "3",
  96. }
  97. components = openshift_image_tag.split(".")
  98. if not components or len(components) < 2:
  99. msg = "An invalid version of OpenShift was found for this host: {}"
  100. raise OpenShiftCheckException(msg.format(openshift_image_tag))
  101. if components[0] in openshift_major_release_version:
  102. components[0] = openshift_major_release_version[components[0]]
  103. components = tuple(int(x) for x in components[:2])
  104. return components
  105. LOADER_EXCLUDES = (
  106. "__init__.py",
  107. "mixins.py",
  108. "logging.py",
  109. )
  110. def load_checks(path=None, subpkg=""):
  111. """Dynamically import all check modules for the side effect of registering checks."""
  112. if path is None:
  113. path = os.path.dirname(__file__)
  114. modules = []
  115. for name in os.listdir(path):
  116. if os.path.isdir(os.path.join(path, name)):
  117. modules = modules + load_checks(os.path.join(path, name), subpkg + "." + name)
  118. continue
  119. if name.endswith(".py") and name not in LOADER_EXCLUDES:
  120. modules.append(import_module(__package__ + subpkg + "." + name[:-3]))
  121. return modules