]>
Commit | Line | Data |
---|---|---|
1adf2230 AA |
1 | import argparse |
2 | from textwrap import dedent | |
3 | from ceph_volume import terminal, decorators | |
4 | from ceph_volume.util import disk, prompt_bool | |
5 | from ceph_volume.util import arg_validators | |
6 | from . import strategies | |
7 | ||
8 | ||
9 | device_list_template = """ | |
10 | * {path: <25} {size: <10} {state}""" | |
11 | ||
12 | ||
13 | def device_formatter(devices): | |
14 | lines = [] | |
15 | for path, details in devices: | |
16 | lines.append(device_list_template.format( | |
17 | path=path, size=details['human_readable_size'], | |
18 | state='solid' if details['rotational'] == '0' else 'rotational') | |
19 | ) | |
20 | ||
21 | return ''.join(lines) | |
22 | ||
23 | ||
24 | # Scenario filtering/detection | |
25 | def bluestore_single_type(device_facts): | |
26 | """ | |
27 | Detect devices that are just HDDs or solid state so that a 1:1 | |
28 | device-to-osd provisioning can be done | |
29 | """ | |
30 | types = [device.sys_api['rotational'] for device in device_facts] | |
31 | if len(set(types)) == 1: | |
32 | return strategies.bluestore.SingleType | |
33 | ||
34 | ||
35 | def bluestore_mixed_type(device_facts): | |
36 | """ | |
37 | Detect if devices are HDDs as well as solid state so that block.db can be | |
38 | placed in solid devices while data is kept in the spinning drives. | |
39 | """ | |
40 | types = [device.sys_api['rotational'] for device in device_facts] | |
41 | if len(set(types)) > 1: | |
42 | return strategies.bluestore.MixedType | |
43 | ||
44 | ||
45 | def filestore_single_type(device_facts): | |
46 | """ | |
47 | Detect devices that are just HDDs or solid state so that a 1:1 | |
48 | device-to-osd provisioning can be done, keeping the journal on the OSD | |
49 | """ | |
50 | types = [device.sys_api['rotational'] for device in device_facts] | |
51 | if len(set(types)) == 1: | |
52 | return strategies.filestore.SingleType | |
53 | ||
54 | ||
55 | def filestore_mixed_type(device_facts): | |
56 | """ | |
57 | Detect if devices are HDDs as well as solid state so that the journal can be | |
58 | placed in solid devices while data is kept in the spinning drives. | |
59 | """ | |
60 | types = [device.sys_api['rotational'] for device in device_facts] | |
61 | if len(set(types)) > 1: | |
62 | return strategies.filestore.MixedType | |
63 | ||
64 | ||
65 | def get_strategy(args): | |
66 | """ | |
67 | Given a set of devices as input, go through the different detection | |
68 | mechanisms to narrow down on a strategy to use. The strategies are 4 in | |
69 | total: | |
70 | ||
71 | * Single device type on Bluestore | |
72 | * Mixed device types on Bluestore | |
73 | * Single device type on Filestore | |
74 | * Mixed device types on Filestore | |
75 | ||
76 | When the function matches to a scenario it returns the strategy class. This | |
77 | allows for dynamic loading of the conditions needed for each scenario, with | |
78 | normalized classes | |
79 | """ | |
80 | bluestore_strategies = [bluestore_mixed_type, bluestore_single_type] | |
81 | filestore_strategies = [filestore_mixed_type, filestore_single_type] | |
82 | if args.bluestore: | |
83 | strategies = bluestore_strategies | |
84 | else: | |
85 | strategies = filestore_strategies | |
86 | ||
87 | for strategy in strategies: | |
88 | backend = strategy(args.devices) | |
89 | if backend: | |
90 | return backend(args.devices, args) | |
91 | ||
92 | ||
93 | class Batch(object): | |
94 | ||
95 | help = 'Automatically size devices for multi-OSD provisioning with minimal interaction' | |
96 | ||
97 | _help = dedent(""" | |
98 | Automatically size devices ready for OSD provisioning based on default strategies. | |
99 | ||
100 | Detected devices: | |
101 | {detected_devices} | |
102 | ||
103 | Usage: | |
104 | ||
105 | ceph-volume lvm batch [DEVICE...] | |
106 | ||
107 | Optional reporting on possible outcomes is enabled with --report | |
108 | ||
109 | ceph-volume lvm batch --report [DEVICE...] | |
110 | """) | |
111 | ||
112 | def __init__(self, argv): | |
113 | self.argv = argv | |
114 | ||
115 | def get_devices(self): | |
116 | all_devices = disk.get_devices() | |
117 | # remove devices with partitions | |
118 | # XXX Should be optional when getting device info | |
119 | for device, detail in all_devices.items(): | |
120 | if detail.get('partitions') != {}: | |
121 | del all_devices[device] | |
122 | devices = sorted(all_devices.items(), key=lambda x: (x[0], x[1]['size'])) | |
123 | return device_formatter(devices) | |
124 | ||
125 | def print_help(self): | |
126 | return self._help.format( | |
127 | detected_devices=self.get_devices(), | |
128 | ) | |
129 | ||
130 | def report(self, args): | |
131 | strategy = get_strategy(args) | |
132 | if args.format == 'pretty': | |
133 | strategy.report_pretty() | |
134 | elif args.format == 'json': | |
135 | strategy.report_json() | |
136 | else: | |
137 | raise RuntimeError('report format must be "pretty" or "json"') | |
138 | ||
139 | def execute(self, args): | |
140 | strategy = get_strategy(args) | |
141 | if not args.yes: | |
142 | strategy.report_pretty() | |
143 | terminal.info('The above OSDs would be created if the operation continues') | |
144 | if not prompt_bool('do you want to proceed? (yes/no)'): | |
145 | terminal.error('aborting OSD provisioning for %s' % ','.join(args.devices)) | |
146 | raise SystemExit(0) | |
147 | ||
148 | strategy.execute() | |
149 | ||
150 | @decorators.needs_root | |
151 | def main(self): | |
152 | parser = argparse.ArgumentParser( | |
153 | prog='ceph-volume lvm batch', | |
154 | formatter_class=argparse.RawDescriptionHelpFormatter, | |
155 | description=self.print_help(), | |
156 | ) | |
157 | ||
158 | parser.add_argument( | |
159 | 'devices', | |
160 | metavar='DEVICES', | |
161 | nargs='*', | |
162 | type=arg_validators.ValidDevice(), | |
163 | default=[], | |
164 | help='Devices to provision OSDs', | |
165 | ) | |
166 | parser.add_argument( | |
167 | '--bluestore', | |
168 | action='store_true', | |
169 | help='bluestore objectstore (default)', | |
170 | ) | |
171 | parser.add_argument( | |
172 | '--filestore', | |
173 | action='store_true', | |
174 | help='filestore objectstore', | |
175 | ) | |
176 | parser.add_argument( | |
177 | '--report', | |
178 | action='store_true', | |
179 | help='Autodetect the objectstore by inspecting the OSD', | |
180 | ) | |
181 | parser.add_argument( | |
182 | '--yes', | |
183 | action='store_true', | |
184 | help='Avoid prompting for confirmation when provisioning', | |
185 | ) | |
186 | parser.add_argument( | |
187 | '--format', | |
188 | help='output format, defaults to "pretty"', | |
189 | default='pretty', | |
190 | choices=['json', 'pretty'], | |
191 | ) | |
192 | parser.add_argument( | |
193 | '--dmcrypt', | |
194 | action='store_true', | |
195 | help='Enable device encryption via dm-crypt', | |
196 | ) | |
197 | parser.add_argument( | |
198 | '--crush-device-class', | |
199 | dest='crush_device_class', | |
200 | help='Crush device class to assign this OSD to', | |
201 | ) | |
202 | parser.add_argument( | |
203 | '--no-systemd', | |
204 | dest='no_systemd', | |
205 | action='store_true', | |
206 | help='Skip creating and enabling systemd units and starting OSD services', | |
207 | ) | |
208 | args = parser.parse_args(self.argv) | |
209 | ||
210 | if not args.devices: | |
211 | return parser.print_help() | |
212 | ||
213 | # Default to bluestore here since defaulting it in add_argument may | |
214 | # cause both to be True | |
215 | if not args.bluestore and not args.filestore: | |
216 | args.bluestore = True | |
217 | ||
218 | if args.report: | |
219 | self.report(args) | |
220 | else: | |
221 | self.execute(args) |