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