disk_availability.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. """Check that there is enough disk space in predefined paths."""
  2. import os.path
  3. import tempfile
  4. from openshift_checks import OpenShiftCheck, OpenShiftCheckException, get_var
  5. class DiskAvailability(OpenShiftCheck):
  6. """Check that recommended disk space is available before a first-time install."""
  7. name = "disk_availability"
  8. tags = ["preflight"]
  9. # Values taken from the official installation documentation:
  10. # https://docs.openshift.org/latest/install_config/install/prerequisites.html#system-requirements
  11. recommended_disk_space_bytes = {
  12. '/var': {
  13. 'masters': 40 * 10**9,
  14. 'nodes': 15 * 10**9,
  15. 'etcd': 20 * 10**9,
  16. },
  17. # Used to copy client binaries into,
  18. # see roles/openshift_cli/library/openshift_container_binary_sync.py.
  19. '/usr/local/bin': {
  20. 'masters': 1 * 10**9,
  21. 'nodes': 1 * 10**9,
  22. 'etcd': 1 * 10**9,
  23. },
  24. # Used as temporary storage in several cases.
  25. tempfile.gettempdir(): {
  26. 'masters': 1 * 10**9,
  27. 'nodes': 1 * 10**9,
  28. 'etcd': 1 * 10**9,
  29. },
  30. }
  31. @classmethod
  32. def is_active(cls, task_vars):
  33. """Skip hosts that do not have recommended disk space requirements."""
  34. group_names = get_var(task_vars, "group_names", default=[])
  35. active_groups = set()
  36. for recommendation in cls.recommended_disk_space_bytes.values():
  37. active_groups.update(recommendation.keys())
  38. has_disk_space_recommendation = bool(active_groups.intersection(group_names))
  39. return super(DiskAvailability, cls).is_active(task_vars) and has_disk_space_recommendation
  40. def run(self, tmp, task_vars):
  41. group_names = get_var(task_vars, "group_names")
  42. ansible_mounts = get_var(task_vars, "ansible_mounts")
  43. ansible_mounts = {mount['mount']: mount for mount in ansible_mounts}
  44. user_config = get_var(task_vars, "openshift_check_min_host_disk_gb", default={})
  45. try:
  46. # For backwards-compatibility, if openshift_check_min_host_disk_gb
  47. # is a number, then it overrides the required config for '/var'.
  48. number = float(user_config)
  49. user_config = {
  50. '/var': {
  51. 'masters': number,
  52. 'nodes': number,
  53. 'etcd': number,
  54. },
  55. }
  56. except TypeError:
  57. # If it is not a number, then it should be a nested dict.
  58. pass
  59. # TODO: as suggested in
  60. # https://github.com/openshift/openshift-ansible/pull/4436#discussion_r122180021,
  61. # maybe we could support checking disk availability in paths that are
  62. # not part of the official recommendation but present in the user
  63. # configuration.
  64. for path, recommendation in self.recommended_disk_space_bytes.items():
  65. free_bytes = self.free_bytes(path, ansible_mounts)
  66. recommended_bytes = max(recommendation.get(name, 0) for name in group_names)
  67. config = user_config.get(path, {})
  68. # NOTE: the user config is in GB, but we compare bytes, thus the
  69. # conversion.
  70. config_bytes = max(config.get(name, 0) for name in group_names) * 10**9
  71. recommended_bytes = config_bytes or recommended_bytes
  72. if free_bytes < recommended_bytes:
  73. free_gb = float(free_bytes) / 10**9
  74. recommended_gb = float(recommended_bytes) / 10**9
  75. return {
  76. 'failed': True,
  77. 'msg': (
  78. 'Available disk space in "{}" ({:.1f} GB) '
  79. 'is below minimum recommended ({:.1f} GB)'
  80. ).format(path, free_gb, recommended_gb)
  81. }
  82. return {}
  83. @staticmethod
  84. def free_bytes(path, ansible_mounts):
  85. """Return the size available in path based on ansible_mounts."""
  86. mount_point = path
  87. # arbitry value to prevent an infinite loop, in the unlike case that '/'
  88. # is not in ansible_mounts.
  89. max_depth = 32
  90. while mount_point not in ansible_mounts and max_depth > 0:
  91. mount_point = os.path.dirname(mount_point)
  92. max_depth -= 1
  93. try:
  94. free_bytes = ansible_mounts[mount_point]['size_available']
  95. except KeyError:
  96. known_mounts = ', '.join('"{}"'.format(mount) for mount in sorted(ansible_mounts)) or 'none'
  97. msg = 'Unable to determine disk availability for "{}". Known mount points: {}.'
  98. raise OpenShiftCheckException(msg.format(path, known_mounts))
  99. return free_bytes