]>
git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/util/arg_validators.py
4 from ceph_volume
import terminal
, decorators
, process
5 from ceph_volume
.util
.device
import Device
6 from ceph_volume
.util
import disk
12 class ValidDevice(object):
14 def __init__(self
, as_string
=False, gpt_ok
=False):
15 self
.as_string
= as_string
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
)
23 def get_device(self
, dev_path
):
24 self
._device
= Device(dev_path
)
25 self
.dev_path
= dev_path
27 def _format_device(self
, device
):
30 # all codepaths expect an lv path to be returned in this format
31 return "{}/{}".format(device
.vg_name
, device
.lv_name
)
35 def _is_valid_device(self
):
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
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
))
50 raise argparse
.ArgumentError(None, error
)
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
())
59 def _is_valid_device(self
, raise_sys_exit
=True):
60 super()._is
_valid
_device
()
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
())
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
))
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
))
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
())
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)
91 terminal
.info("Raw device {} is already prepared.".format(self
.dev_path
))
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
))
96 super()._is
_valid
_device
()
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
())
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(
109 '{} is a partition, please pass '
110 'LVs or raw block devices'.format(self
.dev_path
))
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
())
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']:
126 if self
._device
.used_by_ceph
:
128 super()._is
_valid
_device
(raise_sys_exit
=False)
132 class OSDPath(object):
134 Validate path exists and it looks like an OSD directory.
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
)
143 arg_is_partition
= disk
.is_partition(string
)
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" % (
159 raise argparse
.ArgumentError(None, error
)
161 return os
.path
.abspath(string
)
164 def exclude_group_options(parser
, groups
, argv
=None):
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.
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.
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
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
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
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
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
)
209 if flag
not in flags_to_verify
:
211 for group_name
, flags
in group_flags
.items():
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
221 terminal
.warning(msg
)
222 last_group
= group_name
225 class ValidFraction(object):
227 Validate fraction is in (0, 1.0]
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