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