]> git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/util/prepare.py
update sources to 12.2.8
[ceph.git] / ceph / src / ceph-volume / ceph_volume / util / prepare.py
1 """
2 These utilities for prepare provide all the pieces needed to prepare a device
3 but also a compounded ("single call") helper to do them in order. Some plugins
4 may want to change some part of the process, while others might want to consume
5 the single-call helper
6 """
7 import os
8 import logging
9 import json
10 from ceph_volume import process, conf, __release__, terminal
11 from ceph_volume.util import system, constants, str_to_int, disk
12
13 logger = logging.getLogger(__name__)
14 mlogger = terminal.MultiLogger(__name__)
15
16
17 def create_key():
18 stdout, stderr, returncode = process.call(
19 ['ceph-authtool', '--gen-print-key'],
20 show_command=True)
21 if returncode != 0:
22 raise RuntimeError('Unable to generate a new auth key')
23 return ' '.join(stdout).strip()
24
25
26 def write_keyring(osd_id, secret, keyring_name='keyring', name=None):
27 """
28 Create a keyring file with the ``ceph-authtool`` utility. Constructs the
29 path over well-known conventions for the OSD, and allows any other custom
30 ``name`` to be set.
31
32 :param osd_id: The ID for the OSD to be used
33 :param secret: The key to be added as (as a string)
34 :param name: Defaults to 'osd.{ID}' but can be used to add other client
35 names, specifically for 'lockbox' type of keys
36 :param keyring_name: Alternative keyring name, for supporting other
37 types of keys like for lockbox
38 """
39 osd_keyring = '/var/lib/ceph/osd/%s-%s/%s' % (conf.cluster, osd_id, keyring_name)
40 name = name or 'osd.%s' % str(osd_id)
41 process.run(
42 [
43 'ceph-authtool', osd_keyring,
44 '--create-keyring',
45 '--name', name,
46 '--add-key', secret
47 ])
48 system.chown(osd_keyring)
49
50
51 def get_journal_size(lv_format=True):
52 """
53 Helper to retrieve the size (defined in megabytes in ceph.conf) to create
54 the journal logical volume, it "translates" the string into a float value,
55 then converts that into gigabytes, and finally (optionally) it formats it
56 back as a string so that it can be used for creating the LV.
57
58 :param lv_format: Return a string to be used for ``lv_create``. A 5 GB size
59 would result in '5G', otherwise it will return a ``Size`` object.
60 """
61 conf_journal_size = conf.ceph.get_safe('osd', 'osd_journal_size', '5120')
62 logger.debug('osd_journal_size set to %s' % conf_journal_size)
63 journal_size = disk.Size(mb=str_to_int(conf_journal_size))
64
65 if journal_size < disk.Size(gb=2):
66 mlogger.error('Refusing to continue with configured size for journal')
67 raise RuntimeError('journal sizes must be larger than 2GB, detected: %s' % journal_size)
68 if lv_format:
69 return '%sG' % journal_size.gb.as_int()
70 return journal_size
71
72
73 def create_id(fsid, json_secrets, osd_id=None):
74 """
75 :param fsid: The osd fsid to create, always required
76 :param json_secrets: a json-ready object with whatever secrets are wanted
77 to be passed to the monitor
78 :param osd_id: Reuse an existing ID from an OSD that's been destroyed, if the
79 id does not exist in the cluster a new ID will be created
80 """
81 bootstrap_keyring = '/var/lib/ceph/bootstrap-osd/%s.keyring' % conf.cluster
82 cmd = [
83 'ceph',
84 '--cluster', conf.cluster,
85 '--name', 'client.bootstrap-osd',
86 '--keyring', bootstrap_keyring,
87 '-i', '-',
88 'osd', 'new', fsid
89 ]
90 if osd_id is not None:
91 if osd_id_available(osd_id):
92 cmd.append(osd_id)
93 else:
94 raise RuntimeError("The osd ID {} is already in use or does not exist.".format(osd_id))
95 stdout, stderr, returncode = process.call(
96 cmd,
97 stdin=json_secrets,
98 show_command=True
99 )
100 if returncode != 0:
101 raise RuntimeError('Unable to create a new OSD id')
102 return ' '.join(stdout).strip()
103
104
105 def osd_id_available(osd_id):
106 """
107 Checks to see if an osd ID exists and if it's available for
108 reuse. Returns True if it is, False if it isn't.
109
110 :param osd_id: The osd ID to check
111 """
112 if osd_id is None:
113 return False
114 bootstrap_keyring = '/var/lib/ceph/bootstrap-osd/%s.keyring' % conf.cluster
115 stdout, stderr, returncode = process.call(
116 [
117 'ceph',
118 '--cluster', conf.cluster,
119 '--name', 'client.bootstrap-osd',
120 '--keyring', bootstrap_keyring,
121 'osd',
122 'tree',
123 '-f', 'json',
124 ],
125 show_command=True
126 )
127 if returncode != 0:
128 raise RuntimeError('Unable check if OSD id exists: %s' % osd_id)
129
130 output = json.loads(''.join(stdout).strip())
131 osds = output['nodes']
132 osd = [osd for osd in osds if str(osd['id']) == str(osd_id)]
133 if osd and osd[0].get('status') == "destroyed":
134 return True
135 return False
136
137
138 def mount_tmpfs(path):
139 process.run([
140 'mount',
141 '-t',
142 'tmpfs', 'tmpfs',
143 path
144 ])
145
146 # Restore SELinux context
147 system.set_context(path)
148
149
150 def create_osd_path(osd_id, tmpfs=False):
151 path = '/var/lib/ceph/osd/%s-%s' % (conf.cluster, osd_id)
152 system.mkdir_p('/var/lib/ceph/osd/%s-%s' % (conf.cluster, osd_id))
153 if tmpfs:
154 mount_tmpfs(path)
155
156
157 def format_device(device):
158 # only supports xfs
159 command = ['mkfs', '-t', 'xfs']
160
161 # get the mkfs options if any for xfs,
162 # fallback to the default options defined in constants.mkfs
163 flags = conf.ceph.get_list(
164 'osd',
165 'osd_mkfs_options_xfs',
166 default=constants.mkfs.get('xfs'),
167 split=' ',
168 )
169
170 # always force
171 if '-f' not in flags:
172 flags.insert(0, '-f')
173
174 command.extend(flags)
175 command.append(device)
176 process.run(command)
177
178
179 def _normalize_mount_flags(flags, extras=None):
180 """
181 Mount flag options have to be a single string, separated by a comma. If the
182 flags are separated by spaces, or with commas and spaces in ceph.conf, the
183 mount options will be passed incorrectly.
184
185 This will help when parsing ceph.conf values return something like::
186
187 ["rw,", "exec,"]
188
189 Or::
190
191 [" rw ,", "exec"]
192
193 :param flags: A list of flags, or a single string of mount flags
194 :param extras: Extra set of mount flags, useful when custom devices like VDO need
195 ad-hoc mount configurations
196 """
197 # Instead of using set(), we append to this new list here, because set()
198 # will create an arbitrary order on the items that is made worst when
199 # testing with tools like tox that includes a randomizer seed. By
200 # controlling the order, it is easier to correctly assert the expectation
201 unique_flags = []
202 if isinstance(flags, list):
203 if extras:
204 flags.extend(extras)
205
206 # ensure that spaces and commas are removed so that they can join
207 # correctly, remove duplicates
208 for f in flags:
209 if f and f not in unique_flags:
210 unique_flags.append(f.strip().strip(','))
211 return ','.join(unique_flags)
212
213 # split them, clean them, and join them back again
214 flags = flags.strip().split(' ')
215 if extras:
216 flags.extend(extras)
217
218 # remove possible duplicates
219 for f in flags:
220 if f and f not in unique_flags:
221 unique_flags.append(f.strip().strip(','))
222 flags = ','.join(unique_flags)
223 # Before returning, split them again, since strings can be mashed up
224 # together, preventing removal of duplicate entries
225 return ','.join(set(flags.split(',')))
226
227
228 def mount_osd(device, osd_id, **kw):
229 extras = []
230 is_vdo = kw.get('is_vdo', '0')
231 if is_vdo == '1':
232 extras = ['discard']
233 destination = '/var/lib/ceph/osd/%s-%s' % (conf.cluster, osd_id)
234 command = ['mount', '-t', 'xfs', '-o']
235 flags = conf.ceph.get_list(
236 'osd',
237 'osd_mount_options_xfs',
238 default=constants.mount.get('xfs'),
239 split=' ',
240 )
241 command.append(
242 _normalize_mount_flags(flags, extras=extras)
243 )
244 command.append(device)
245 command.append(destination)
246 process.run(command)
247
248 # Restore SELinux context
249 system.set_context(destination)
250
251
252 def _link_device(device, device_type, osd_id):
253 """
254 Allow linking any device type in an OSD directory. ``device`` must the be
255 source, with an absolute path and ``device_type`` will be the destination
256 name, like 'journal', or 'block'
257 """
258 device_path = '/var/lib/ceph/osd/%s-%s/%s' % (
259 conf.cluster,
260 osd_id,
261 device_type
262 )
263 command = ['ln', '-s', device, device_path]
264 system.chown(device)
265
266 process.run(command)
267
268
269 def link_journal(journal_device, osd_id):
270 _link_device(journal_device, 'journal', osd_id)
271
272
273 def link_block(block_device, osd_id):
274 _link_device(block_device, 'block', osd_id)
275
276
277 def link_wal(wal_device, osd_id):
278 _link_device(wal_device, 'block.wal', osd_id)
279
280
281 def link_db(db_device, osd_id):
282 _link_device(db_device, 'block.db', osd_id)
283
284
285 def get_monmap(osd_id):
286 """
287 Before creating the OSD files, a monmap needs to be retrieved so that it
288 can be used to tell the monitor(s) about the new OSD. A call will look like::
289
290 ceph --cluster ceph --name client.bootstrap-osd \
291 --keyring /var/lib/ceph/bootstrap-osd/ceph.keyring \
292 mon getmap -o /var/lib/ceph/osd/ceph-0/activate.monmap
293 """
294 path = '/var/lib/ceph/osd/%s-%s/' % (conf.cluster, osd_id)
295 bootstrap_keyring = '/var/lib/ceph/bootstrap-osd/%s.keyring' % conf.cluster
296 monmap_destination = os.path.join(path, 'activate.monmap')
297
298 process.run([
299 'ceph',
300 '--cluster', conf.cluster,
301 '--name', 'client.bootstrap-osd',
302 '--keyring', bootstrap_keyring,
303 'mon', 'getmap', '-o', monmap_destination
304 ])
305
306
307 def osd_mkfs_bluestore(osd_id, fsid, keyring=None, wal=False, db=False):
308 """
309 Create the files for the OSD to function. A normal call will look like:
310
311 ceph-osd --cluster ceph --mkfs --mkkey -i 0 \
312 --monmap /var/lib/ceph/osd/ceph-0/activate.monmap \
313 --osd-data /var/lib/ceph/osd/ceph-0 \
314 --osd-uuid 8d208665-89ae-4733-8888-5d3bfbeeec6c \
315 --keyring /var/lib/ceph/osd/ceph-0/keyring \
316 --setuser ceph --setgroup ceph
317
318 In some cases it is required to use the keyring, when it is passed in as
319 a keywork argument it is used as part of the ceph-osd command
320 """
321 path = '/var/lib/ceph/osd/%s-%s/' % (conf.cluster, osd_id)
322 monmap = os.path.join(path, 'activate.monmap')
323
324 system.chown(path)
325
326 base_command = [
327 'ceph-osd',
328 '--cluster', conf.cluster,
329 # undocumented flag, sets the `type` file to contain 'bluestore'
330 '--osd-objectstore', 'bluestore',
331 '--mkfs',
332 '-i', osd_id,
333 '--monmap', monmap,
334 ]
335
336 supplementary_command = [
337 '--osd-data', path,
338 '--osd-uuid', fsid,
339 '--setuser', 'ceph',
340 '--setgroup', 'ceph'
341 ]
342
343 if keyring is not None:
344 base_command.extend(['--keyfile', '-'])
345
346 if wal:
347 base_command.extend(
348 ['--bluestore-block-wal-path', wal]
349 )
350 system.chown(wal)
351
352 if db:
353 base_command.extend(
354 ['--bluestore-block-db-path', db]
355 )
356 system.chown(db)
357
358 command = base_command + supplementary_command
359
360 _, _, returncode = process.call(command, stdin=keyring, show_command=True)
361 if returncode != 0:
362 raise RuntimeError('Command failed with exit code %s: %s' % (returncode, ' '.join(command)))
363
364
365 def osd_mkfs_filestore(osd_id, fsid, keyring):
366 """
367 Create the files for the OSD to function. A normal call will look like:
368
369 ceph-osd --cluster ceph --mkfs --mkkey -i 0 \
370 --monmap /var/lib/ceph/osd/ceph-0/activate.monmap \
371 --osd-data /var/lib/ceph/osd/ceph-0 \
372 --osd-journal /var/lib/ceph/osd/ceph-0/journal \
373 --osd-uuid 8d208665-89ae-4733-8888-5d3bfbeeec6c \
374 --keyring /var/lib/ceph/osd/ceph-0/keyring \
375 --setuser ceph --setgroup ceph
376
377 """
378 path = '/var/lib/ceph/osd/%s-%s/' % (conf.cluster, osd_id)
379 monmap = os.path.join(path, 'activate.monmap')
380 journal = os.path.join(path, 'journal')
381
382 system.chown(journal)
383 system.chown(path)
384
385 command = [
386 'ceph-osd',
387 '--cluster', conf.cluster,
388 # undocumented flag, sets the `type` file to contain 'filestore'
389 '--osd-objectstore', 'filestore',
390 '--mkfs',
391 '-i', osd_id,
392 '--monmap', monmap,
393 ]
394
395 if __release__ != 'luminous':
396 # goes through stdin
397 command.extend(['--keyfile', '-'])
398
399 command.extend([
400 '--osd-data', path,
401 '--osd-journal', journal,
402 '--osd-uuid', fsid,
403 '--setuser', 'ceph',
404 '--setgroup', 'ceph'
405 ])
406
407 _, _, returncode = process.call(
408 command, stdin=keyring, terminal_verbose=True, show_command=True
409 )
410 if returncode != 0:
411 raise RuntimeError('Command failed with exit code %s: %s' % (returncode, ' '.join(command)))