]>
Commit | Line | Data |
---|---|---|
b5b8bbf5 | 1 | import argparse |
3efd9988 FG |
2 | import os |
3 | from ceph_volume import terminal | |
4 | from ceph_volume import decorators | |
5 | from ceph_volume.util import disk | |
1adf2230 | 6 | from ceph_volume.util.device import Device |
b5b8bbf5 FG |
7 | |
8 | ||
91327a77 | 9 | class ValidDevice(object): |
3efd9988 | 10 | |
f64942e4 | 11 | def __init__(self, as_string=False, gpt_ok=False): |
91327a77 | 12 | self.as_string = as_string |
f64942e4 | 13 | self.gpt_ok = gpt_ok |
b5b8bbf5 | 14 | |
f91f0fd5 TL |
15 | def __call__(self, dev_path): |
16 | device = self._is_valid_device(dev_path) | |
17 | return self._format_device(device) | |
18 | ||
19 | def _format_device(self, device): | |
20 | if self.as_string: | |
21 | if device.is_lv: | |
22 | # all codepaths expect an lv path to be returned in this format | |
23 | return "{}/{}".format(device.vg_name, device.lv_name) | |
24 | return device.path | |
25 | return device | |
26 | ||
27 | def _is_valid_device(self, dev_path): | |
28 | device = Device(dev_path) | |
b5b8bbf5 | 29 | error = None |
91327a77 | 30 | if not device.exists: |
f91f0fd5 | 31 | error = "Unable to proceed with non-existing device: %s" % dev_path |
f64942e4 AA |
32 | # FIXME this is not a nice API, this validator was meant to catch any |
33 | # non-existing devices upfront, not check for gpt headers. Now this | |
34 | # needs to optionally skip checking gpt headers which is beyond | |
35 | # verifying if the device exists. The better solution would be to | |
36 | # configure this with a list of checks that can be excluded/included on | |
37 | # __init__ | |
38 | elif device.has_gpt_headers and not self.gpt_ok: | |
f91f0fd5 | 39 | error = "GPT headers found, they must be removed on: %s" % dev_path |
b5b8bbf5 FG |
40 | |
41 | if error: | |
42 | raise argparse.ArgumentError(None, error) | |
1adf2230 AA |
43 | |
44 | return device | |
45 | ||
46 | ||
f91f0fd5 TL |
47 | class ValidBatchDevice(ValidDevice): |
48 | ||
49 | def __call__(self, dev_path): | |
50 | dev = self._is_valid_device(dev_path) | |
51 | if dev.is_partition: | |
52 | raise argparse.ArgumentError( | |
53 | None, | |
54 | '{} is a partition, please pass ' | |
55 | 'LVs or raw block devices'.format(dev_path)) | |
56 | return self._format_device(dev) | |
57 | ||
58 | ||
3efd9988 FG |
59 | class OSDPath(object): |
60 | """ | |
61 | Validate path exists and it looks like an OSD directory. | |
62 | """ | |
63 | ||
64 | @decorators.needs_root | |
65 | def __call__(self, string): | |
66 | if not os.path.exists(string): | |
67 | error = "Path does not exist: %s" % string | |
68 | raise argparse.ArgumentError(None, error) | |
69 | ||
70 | arg_is_partition = disk.is_partition(string) | |
71 | if arg_is_partition: | |
72 | return os.path.abspath(string) | |
73 | absolute_path = os.path.abspath(string) | |
74 | if not os.path.isdir(absolute_path): | |
75 | error = "Argument is not a directory or device which is required to scan" | |
76 | raise argparse.ArgumentError(None, error) | |
77 | key_files = ['ceph_fsid', 'fsid', 'keyring', 'ready', 'type', 'whoami'] | |
78 | dir_files = os.listdir(absolute_path) | |
79 | for key_file in key_files: | |
80 | if key_file not in dir_files: | |
81 | terminal.error('All following files must exist in path: %s' % ' '.join(key_files)) | |
82 | error = "Required file (%s) was not found in OSD dir path: %s" % ( | |
83 | key_file, | |
84 | absolute_path | |
85 | ) | |
86 | raise argparse.ArgumentError(None, error) | |
87 | ||
88 | return os.path.abspath(string) | |
3a9019d9 FG |
89 | |
90 | ||
91 | def exclude_group_options(parser, groups, argv=None): | |
92 | """ | |
93 | ``argparse`` has the ability to check for mutually exclusive options, but | |
94 | it only allows a basic XOR behavior: only one flag can be used from | |
95 | a defined group of options. This doesn't help when two groups of options | |
96 | need to be separated. For example, with filestore and bluestore, neither | |
97 | set can be used in conjunction with the other set. | |
98 | ||
99 | This helper validator will consume the parser to inspect the group flags, | |
100 | and it will group them together from ``groups``. This allows proper error | |
101 | reporting, matching each incompatible flag with its group name. | |
102 | ||
103 | :param parser: The argparse object, once it has configured all flags. It is | |
104 | required to contain the group names being used to validate. | |
105 | :param groups: A list of group names (at least two), with the same used for | |
106 | ``add_argument_group`` | |
107 | :param argv: Consume the args (sys.argv) directly from this argument | |
108 | ||
109 | .. note: **Unfortunately** this will not be able to validate correctly when | |
110 | using default flags. In the case of filestore vs. bluestore, ceph-volume | |
111 | defaults to --bluestore, but we can't check that programmatically, we can | |
112 | only parse the flags seen via argv | |
113 | """ | |
114 | # Reduce the parser groups to only the groups we need to intersect | |
115 | parser_groups = [g for g in parser._action_groups if g.title in groups] | |
116 | # A mapping of the group name to flags/options | |
117 | group_flags = {} | |
118 | flags_to_verify = [] | |
119 | for group in parser_groups: | |
120 | # option groups may have more than one item in ``option_strings``, this | |
121 | # will loop over ``_group_actions`` which contains the | |
122 | # ``option_strings``, like ``['--filestore']`` | |
123 | group_flags[group.title] = [ | |
124 | option for group_action in group._group_actions | |
125 | for option in group_action.option_strings | |
126 | ] | |
127 | ||
128 | # Gather all the flags present in the groups so that we only check on those. | |
129 | for flags in group_flags.values(): | |
130 | flags_to_verify.extend(flags) | |
131 | ||
132 | seen = [] | |
133 | last_flag = None | |
134 | last_group = None | |
135 | for flag in argv: | |
136 | if flag not in flags_to_verify: | |
137 | continue | |
138 | for group_name, flags in group_flags.items(): | |
139 | if flag in flags: | |
140 | seen.append(group_name) | |
141 | # We are mutually excluding groups, so having more than 1 group | |
142 | # in ``seen`` means we must raise an error | |
143 | if len(set(seen)) == len(groups): | |
144 | terminal.warning('Incompatible flags were found, some values may get ignored') | |
145 | msg = 'Cannot use %s (%s) with %s (%s)' % ( | |
146 | last_flag, last_group, flag, group_name | |
147 | ) | |
148 | terminal.warning(msg) | |
149 | last_group = group_name | |
150 | last_flag = flag |