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