]> git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/util/arg_validators.py
update ceph source to reef 18.2.0
[ceph.git] / ceph / src / ceph-volume / ceph_volume / util / arg_validators.py
1 import argparse
2 import os
3 import math
4 from ceph_volume import terminal, decorators, process
5 from ceph_volume.util.device import Device
6 from ceph_volume.util import disk
7
8
9 def valid_osd_id(val):
10 return str(int(val))
11
12 class ValidDevice(object):
13
14 def __init__(self, as_string=False, gpt_ok=False):
15 self.as_string = as_string
16 self.gpt_ok = gpt_ok
17
18 def __call__(self, dev_path):
19 self.get_device(dev_path)
20 self._validated_device = self._is_valid_device()
21 return self._format_device(self._validated_device)
22
23 def get_device(self, dev_path):
24 self._device = Device(dev_path)
25 self.dev_path = dev_path
26
27 def _format_device(self, device):
28 if self.as_string:
29 if device.is_lv:
30 # all codepaths expect an lv path to be returned in this format
31 return "{}/{}".format(device.vg_name, device.lv_name)
32 return device.path
33 return device
34
35 def _is_valid_device(self):
36 error = None
37 if not self._device.exists:
38 error = "Unable to proceed with non-existing device: %s" % self.dev_path
39 # FIXME this is not a nice API, this validator was meant to catch any
40 # non-existing devices upfront, not check for gpt headers. Now this
41 # needs to optionally skip checking gpt headers which is beyond
42 # verifying if the device exists. The better solution would be to
43 # configure this with a list of checks that can be excluded/included on
44 # __init__
45 elif self._device.has_gpt_headers and not self.gpt_ok:
46 error = "GPT headers found, they must be removed on: %s" % self.dev_path
47 if self._device.has_partitions:
48 raise RuntimeError("Device {} has partitions.".format(self.dev_path))
49 if error:
50 raise argparse.ArgumentError(None, error)
51 return self._device
52
53
54 class ValidZapDevice(ValidDevice):
55 def __call__(self, dev_path):
56 super().get_device(dev_path)
57 return self._format_device(self._is_valid_device())
58
59 def _is_valid_device(self, raise_sys_exit=True):
60 super()._is_valid_device()
61 return self._device
62
63
64 class ValidDataDevice(ValidDevice):
65 def __call__(self, dev_path):
66 super().get_device(dev_path)
67 return self._format_device(self._is_valid_device())
68
69 def _is_valid_device(self, raise_sys_exit=True):
70 super()._is_valid_device()
71 if self._device.used_by_ceph:
72 terminal.info('Device {} is already prepared'.format(self.dev_path))
73 if raise_sys_exit:
74 raise SystemExit(0)
75 if self._device.has_fs and not self._device.used_by_ceph:
76 raise RuntimeError("Device {} has a filesystem.".format(self.dev_path))
77 if self.dev_path[0] == '/' and disk.has_bluestore_label(self.dev_path):
78 raise RuntimeError("Device {} has bluestore signature.".format(self.dev_path))
79 return self._device
80
81 class ValidRawDevice(ValidDevice):
82 def __call__(self, dev_path):
83 super().get_device(dev_path)
84 return self._format_device(self._is_valid_device())
85
86 def _is_valid_device(self, raise_sys_exit=True):
87 out, err, rc = process.call([
88 'ceph-bluestore-tool', 'show-label',
89 '--dev', self.dev_path], verbose_on_failure=False)
90 if not rc:
91 terminal.info("Raw device {} is already prepared.".format(self.dev_path))
92 raise SystemExit(0)
93 if disk.blkid(self.dev_path).get('TYPE') == 'crypto_LUKS':
94 terminal.info("Raw device {} might already be in use for a dmcrypt OSD, skipping.".format(self.dev_path))
95 raise SystemExit(0)
96 super()._is_valid_device()
97 return self._device
98
99 class ValidBatchDevice(ValidDevice):
100 def __call__(self, dev_path):
101 super().get_device(dev_path)
102 return self._format_device(self._is_valid_device())
103
104 def _is_valid_device(self, raise_sys_exit=False):
105 super()._is_valid_device()
106 if self._device.is_partition:
107 raise argparse.ArgumentError(
108 None,
109 '{} is a partition, please pass '
110 'LVs or raw block devices'.format(self.dev_path))
111 return self._device
112
113
114 class ValidBatchDataDevice(ValidBatchDevice, ValidDataDevice):
115 def __call__(self, dev_path):
116 super().get_device(dev_path)
117 return self._format_device(self._is_valid_device())
118
119 def _is_valid_device(self):
120 # if device is already used by ceph,
121 # leave the validation to Batch.get_deployment_layout()
122 # This way the idempotency isn't broken (especially when using --osds-per-device)
123 for lv in self._device.lvs:
124 if lv.tags.get('ceph.type') in ['db', 'wal']:
125 return self._device
126 if self._device.used_by_ceph:
127 return self._device
128 super()._is_valid_device(raise_sys_exit=False)
129 return self._device
130
131
132 class OSDPath(object):
133 """
134 Validate path exists and it looks like an OSD directory.
135 """
136
137 @decorators.needs_root
138 def __call__(self, string):
139 if not os.path.exists(string):
140 error = "Path does not exist: %s" % string
141 raise argparse.ArgumentError(None, error)
142
143 arg_is_partition = disk.is_partition(string)
144 if arg_is_partition:
145 return os.path.abspath(string)
146 absolute_path = os.path.abspath(string)
147 if not os.path.isdir(absolute_path):
148 error = "Argument is not a directory or device which is required to scan"
149 raise argparse.ArgumentError(None, error)
150 key_files = ['ceph_fsid', 'fsid', 'keyring', 'ready', 'type', 'whoami']
151 dir_files = os.listdir(absolute_path)
152 for key_file in key_files:
153 if key_file not in dir_files:
154 terminal.error('All following files must exist in path: %s' % ' '.join(key_files))
155 error = "Required file (%s) was not found in OSD dir path: %s" % (
156 key_file,
157 absolute_path
158 )
159 raise argparse.ArgumentError(None, error)
160
161 return os.path.abspath(string)
162
163
164 def exclude_group_options(parser, groups, argv=None):
165 """
166 ``argparse`` has the ability to check for mutually exclusive options, but
167 it only allows a basic XOR behavior: only one flag can be used from
168 a defined group of options. This doesn't help when two groups of options
169 need to be separated. For example, with filestore and bluestore, neither
170 set can be used in conjunction with the other set.
171
172 This helper validator will consume the parser to inspect the group flags,
173 and it will group them together from ``groups``. This allows proper error
174 reporting, matching each incompatible flag with its group name.
175
176 :param parser: The argparse object, once it has configured all flags. It is
177 required to contain the group names being used to validate.
178 :param groups: A list of group names (at least two), with the same used for
179 ``add_argument_group``
180 :param argv: Consume the args (sys.argv) directly from this argument
181
182 .. note: **Unfortunately** this will not be able to validate correctly when
183 using default flags. In the case of filestore vs. bluestore, ceph-volume
184 defaults to --bluestore, but we can't check that programmatically, we can
185 only parse the flags seen via argv
186 """
187 # Reduce the parser groups to only the groups we need to intersect
188 parser_groups = [g for g in parser._action_groups if g.title in groups]
189 # A mapping of the group name to flags/options
190 group_flags = {}
191 flags_to_verify = []
192 for group in parser_groups:
193 # option groups may have more than one item in ``option_strings``, this
194 # will loop over ``_group_actions`` which contains the
195 # ``option_strings``, like ``['--filestore']``
196 group_flags[group.title] = [
197 option for group_action in group._group_actions
198 for option in group_action.option_strings
199 ]
200
201 # Gather all the flags present in the groups so that we only check on those.
202 for flags in group_flags.values():
203 flags_to_verify.extend(flags)
204
205 seen = []
206 last_flag = None
207 last_group = None
208 for flag in argv:
209 if flag not in flags_to_verify:
210 continue
211 for group_name, flags in group_flags.items():
212 if flag in flags:
213 seen.append(group_name)
214 # We are mutually excluding groups, so having more than 1 group
215 # in ``seen`` means we must raise an error
216 if len(set(seen)) == len(groups):
217 terminal.warning('Incompatible flags were found, some values may get ignored')
218 msg = 'Cannot use %s (%s) with %s (%s)' % (
219 last_flag, last_group, flag, group_name
220 )
221 terminal.warning(msg)
222 last_group = group_name
223 last_flag = flag
224
225 class ValidFraction(object):
226 """
227 Validate fraction is in (0, 1.0]
228 """
229
230 def __call__(self, fraction):
231 fraction_float = float(fraction)
232 if math.isnan(fraction_float) or fraction_float <= 0.0 or fraction_float > 1.0:
233 raise argparse.ArgumentError(None, 'Fraction %f not in (0,1.0]' % fraction_float)
234 return fraction_float