]>
Commit | Line | Data |
---|---|---|
b5b8bbf5 | 1 | import argparse |
3efd9988 | 2 | import os |
20effc67 | 3 | import math |
33c7a0ef | 4 | from ceph_volume import terminal, decorators, process |
1adf2230 | 5 | from ceph_volume.util.device import Device |
33c7a0ef | 6 | from ceph_volume.util import disk |
f38dd50b TL |
7 | from ceph_volume.util.encryption import set_dmcrypt_no_workqueue |
8 | from ceph_volume import process, conf | |
b5b8bbf5 | 9 | |
20effc67 TL |
10 | def valid_osd_id(val): |
11 | return str(int(val)) | |
12 | ||
f38dd50b TL |
13 | class 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 | 21 | class 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 |
63 | class 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 | ||
73 | class 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 | ||
90 | class 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 | ||
108 | class 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 | ||
123 | class 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 |
141 | class 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 | ||
173 | def 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 | |
234 | class 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 |