Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / ceph / src / ceph-disk / ceph_disk / main.py
1 #!/usr/bin/env python
2 #
3 # Copyright (C) 2015, 2016, 2017 Red Hat <contact@redhat.com>
4 # Copyright (C) 2014 Inktank <info@inktank.com>
5 # Copyright (C) 2014 Cloudwatt <libre.licensing@cloudwatt.com>
6 # Copyright (C) 2014 Catalyst.net Ltd
7 #
8 # Author: Loic Dachary <loic@dachary.org>
9 #
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU Library Public License as published by
12 # the Free Software Foundation; either version 2, or (at your option)
13 # any later version.
14 #
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 # GNU Library Public License for more details.
19 #
20
21 from __future__ import print_function
22
23 import argparse
24 import base64
25 import errno
26 import fcntl
27 import functools
28 import json
29 import logging
30 import os
31 import platform
32 import re
33 import subprocess
34 import stat
35 import sys
36 import tempfile
37 import uuid
38 import time
39 import shlex
40 import shutil
41 import pwd
42 import grp
43 import textwrap
44 import glob
45
46 CEPH_OSD_ONDISK_MAGIC = 'ceph osd volume v026'
47 CEPH_LOCKBOX_ONDISK_MAGIC = 'ceph lockbox volume v001'
48
49 KEY_MANAGEMENT_MODE_V1 = 'ceph-mon v1'
50
51 PTYPE = {
52     'regular': {
53         'journal': {
54             # identical because creating a journal is atomic
55             'ready': '45b0969e-9b03-4f30-b4c6-b4b80ceff106',
56             'tobe': '45b0969e-9b03-4f30-b4c6-b4b80ceff106',
57         },
58         'block': {
59             # identical because creating a block is atomic
60             'ready': 'cafecafe-9b03-4f30-b4c6-b4b80ceff106',
61             'tobe': 'cafecafe-9b03-4f30-b4c6-b4b80ceff106',
62         },
63         'block.db': {
64             # identical because creating a block is atomic
65             'ready': '30cd0809-c2b2-499c-8879-2d6b78529876',
66             'tobe': '30cd0809-c2b2-499c-8879-2d6b785292be',
67         },
68         'block.wal': {
69             # identical because creating a block is atomic
70             'ready': '5ce17fce-4087-4169-b7ff-056cc58473f9',
71             'tobe': '5ce17fce-4087-4169-b7ff-056cc58472be',
72         },
73         'osd': {
74             'ready': '4fbd7e29-9d25-41b8-afd0-062c0ceff05d',
75             'tobe': '89c57f98-2fe5-4dc0-89c1-f3ad0ceff2be',
76         },
77         'lockbox': {
78             'ready': 'fb3aabf9-d25f-47cc-bf5e-721d1816496b',
79             'tobe': 'fb3aabf9-d25f-47cc-bf5e-721d181642be',
80         },
81     },
82     'luks': {
83         'journal': {
84             'ready': '45b0969e-9b03-4f30-b4c6-35865ceff106',
85             'tobe': '89c57f98-2fe5-4dc0-89c1-35865ceff2be',
86         },
87         'block': {
88             'ready': 'cafecafe-9b03-4f30-b4c6-35865ceff106',
89             'tobe': '89c57f98-2fe5-4dc0-89c1-35865ceff2be',
90         },
91         'block.db': {
92             'ready': '166418da-c469-4022-adf4-b30afd37f176',
93             'tobe': '7521c784-4626-4260-bc8d-ba77a0f5f2be',
94         },
95         'block.wal': {
96             'ready': '86a32090-3647-40b9-bbbd-38d8c573aa86',
97             'tobe': '92dad30f-175b-4d40-a5b0-5c0a258b42be',
98         },
99         'osd': {
100             'ready': '4fbd7e29-9d25-41b8-afd0-35865ceff05d',
101             'tobe': '89c57f98-2fe5-4dc0-89c1-5ec00ceff2be',
102         },
103     },
104     'plain': {
105         'journal': {
106             'ready': '45b0969e-9b03-4f30-b4c6-5ec00ceff106',
107             'tobe': '89c57f98-2fe5-4dc0-89c1-35865ceff2be',
108         },
109         'block': {
110             'ready': 'cafecafe-9b03-4f30-b4c6-5ec00ceff106',
111             'tobe': '89c57f98-2fe5-4dc0-89c1-35865ceff2be',
112         },
113         'block.db': {
114             'ready': '93b0052d-02d9-4d8a-a43b-33a3ee4dfbc3',
115             'tobe': '69d17c68-3e58-4399-aff0-b68265f2e2be',
116         },
117         'block.wal': {
118             'ready': '306e8683-4fe2-4330-b7c0-00a917c16966',
119             'tobe': 'f2d89683-a621-4063-964a-eb1f7863a2be',
120         },
121         'osd': {
122             'ready': '4fbd7e29-9d25-41b8-afd0-5ec00ceff05d',
123             'tobe': '89c57f98-2fe5-4dc0-89c1-5ec00ceff2be',
124         },
125     },
126     'mpath': {
127         'journal': {
128             'ready': '45b0969e-8ae0-4982-bf9d-5a8d867af560',
129             'tobe': '45b0969e-8ae0-4982-bf9d-5a8d867af560',
130         },
131         'block': {
132             'ready': 'cafecafe-8ae0-4982-bf9d-5a8d867af560',
133             'tobe': 'cafecafe-8ae0-4982-bf9d-5a8d867af560',
134         },
135         'block.db': {
136             'ready': 'ec6d6385-e346-45dc-be91-da2a7c8b3261',
137             'tobe': 'ec6d6385-e346-45dc-be91-da2a7c8b32be',
138         },
139         'block.wal': {
140             'ready': '01b41e1b-002a-453c-9f17-88793989ff8f',
141             'tobe': '01b41e1b-002a-453c-9f17-88793989f2be',
142         },
143         'osd': {
144             'ready': '4fbd7e29-8ae0-4982-bf9d-5a8d867af560',
145             'tobe': '89c57f98-8ae0-4982-bf9d-5a8d867af560',
146         },
147         'lockbox': {
148             'ready': '7f4a666a-16f3-47a2-8445-152ef4d03f6c',
149             'tobe': '7f4a666a-16f3-47a2-8445-152ef4d032be',
150         },
151     },
152 }
153
154 try:
155     # see https://bugs.python.org/issue23098
156     os.major(0x80002b00)
157 except OverflowError:
158     os.major = lambda devid: ((devid >> 8) & 0xfff) | ((devid >> 32) & ~0xfff)
159     os.minor = lambda devid: (devid & 0xff) | ((devid >> 12) & ~0xff)
160
161
162 class Ptype(object):
163
164     @staticmethod
165     def get_ready_by_type(what):
166         return [x['ready'] for x in PTYPE[what].values()]
167
168     @staticmethod
169     def get_ready_by_name(name):
170         return [x[name]['ready'] for x in PTYPE.values() if name in x]
171
172     @staticmethod
173     def is_regular_space(ptype):
174         return Ptype.is_what_space('regular', ptype)
175
176     @staticmethod
177     def is_mpath_space(ptype):
178         return Ptype.is_what_space('mpath', ptype)
179
180     @staticmethod
181     def is_plain_space(ptype):
182         return Ptype.is_what_space('plain', ptype)
183
184     @staticmethod
185     def is_luks_space(ptype):
186         return Ptype.is_what_space('luks', ptype)
187
188     @staticmethod
189     def is_what_space(what, ptype):
190         for name in Space.NAMES:
191             if ptype == PTYPE[what][name]['ready']:
192                 return True
193         return False
194
195     @staticmethod
196     def space_ptype_to_name(ptype):
197         for what in PTYPE.values():
198             for name in Space.NAMES:
199                 if ptype == what[name]['ready']:
200                     return name
201         raise ValueError('ptype ' + ptype + ' not found')
202
203     @staticmethod
204     def is_dmcrypt_space(ptype):
205         for name in Space.NAMES:
206             if Ptype.is_dmcrypt(ptype, name):
207                 return True
208         return False
209
210     @staticmethod
211     def is_dmcrypt(ptype, name):
212         for what in ('plain', 'luks'):
213             if ptype == PTYPE[what][name]['ready']:
214                 return True
215         return False
216
217
218 SYSFS = '/sys'
219
220 if platform.system() == 'FreeBSD':
221     FREEBSD = True
222     DEFAULT_FS_TYPE = 'zfs'
223     PROCDIR = '/compat/linux/proc'
224     # FreeBSD does not have blockdevices any more
225     BLOCKDIR = '/dev'
226     ROOTGROUP = 'wheel'
227 else:
228     FREEBSD = False
229     DEFAULT_FS_TYPE = 'xfs'
230     PROCDIR = '/proc'
231     BLOCKDIR = '/sys/block'
232     ROOTGROUP = 'root'
233
234 """
235 OSD STATUS Definition
236 """
237 OSD_STATUS_OUT_DOWN = 0
238 OSD_STATUS_OUT_UP = 1
239 OSD_STATUS_IN_DOWN = 2
240 OSD_STATUS_IN_UP = 3
241
242 MOUNT_OPTIONS = dict(
243     btrfs='noatime,user_subvol_rm_allowed',
244     # user_xattr is default ever since linux 2.6.39 / 3.0, but we'll
245     # delay a moment before removing it fully because we did have some
246     # issues with ext4 before the xatts-in-leveldb work, and it seemed
247     # that user_xattr helped
248     ext4='noatime,user_xattr',
249     xfs='noatime,inode64',
250 )
251
252 MKFS_ARGS = dict(
253     btrfs=[
254         # btrfs requires -f, for the same reason as xfs (see comment below)
255         '-f',
256         '-m', 'single',
257         '-l', '32768',
258         '-n', '32768',
259     ],
260     xfs=[
261         # xfs insists on not overwriting previous fs; even if we wipe
262         # partition table, we often recreate it exactly the same way,
263         # so we'll see ghosts of filesystems past
264         '-f',
265         '-i', 'size=2048',
266     ],
267     zfs=[
268         '-o', 'atime=off'
269     ],
270 )
271
272 INIT_SYSTEMS = [
273     'upstart',
274     'sysvinit',
275     'systemd',
276     'openrc',
277     'bsdrc',
278     'auto',
279     'none',
280 ]
281
282 STATEDIR = '/var/lib/ceph'
283
284 SYSCONFDIR = '/etc/ceph'
285
286 prepare_lock = None
287 activate_lock = None
288 SUPPRESS_PREFIX = None
289
290 # only warn once about some things
291 warned_about = {}
292
293 # Nuke the TERM variable to avoid confusing any subprocesses we call.
294 # For example, libreadline will print weird control sequences for some
295 # TERM values.
296 if 'TERM' in os.environ:
297     del os.environ['TERM']
298
299 LOG_NAME = __name__
300 if LOG_NAME == '__main__':
301     LOG_NAME = os.path.basename(sys.argv[0])
302 LOG = logging.getLogger(LOG_NAME)
303
304 # Allow user-preferred values for subprocess user and group
305 CEPH_PREF_USER = None
306 CEPH_PREF_GROUP = None
307
308
309 class FileLock(object):
310     def __init__(self, fn):
311         self.fn = fn
312         self.fd = None
313
314     def __enter__(self):
315         assert not self.fd
316         self.fd = os.open(self.fn, os.O_WRONLY | os.O_CREAT)
317         fcntl.lockf(self.fd, fcntl.LOCK_EX)
318
319     def __exit__(self, exc_type, exc_val, exc_tb):
320         assert self.fd
321         fcntl.lockf(self.fd, fcntl.LOCK_UN)
322         os.close(self.fd)
323         self.fd = None
324
325
326 class Error(Exception):
327     """
328     Error
329     """
330
331     def __str__(self):
332         doc = _bytes2str(self.__doc__.strip())
333         try:
334             str_type = basestring
335         except NameError:
336             str_type = str
337         args = [a if isinstance(a, str_type) else str(a) for a in self.args]
338         return ': '.join([doc] + [_bytes2str(a) for a in args])
339
340
341 class MountError(Error):
342     """
343     Mounting filesystem failed
344     """
345
346
347 class UnmountError(Error):
348     """
349     Unmounting filesystem failed
350     """
351
352
353 class BadMagicError(Error):
354     """
355     Does not look like a Ceph OSD, or incompatible version
356     """
357
358
359 class TruncatedLineError(Error):
360     """
361     Line is truncated
362     """
363
364
365 class TooManyLinesError(Error):
366     """
367     Too many lines
368     """
369
370
371 class FilesystemTypeError(Error):
372     """
373     Cannot discover filesystem type
374      """
375
376
377 class CephDiskException(Exception):
378     """
379     A base exception for ceph-disk to provide custom (ad-hoc) messages that
380     will be caught and dealt with when main() is executed
381     """
382     pass
383
384
385 class ExecutableNotFound(CephDiskException):
386     """
387     Exception to report on executables not available in PATH
388     """
389     pass
390
391
392 def is_systemd():
393     """
394     Detect whether systemd is running
395     """
396     with open(PROCDIR + '/1/comm', 'r') as f:
397         return 'systemd' in f.read()
398
399
400 def is_upstart():
401     """
402     Detect whether upstart is running
403     """
404     (out, err, _) = command(['init', '--version'])
405     return 'upstart' in out
406
407
408 def maybe_mkdir(*a, **kw):
409     """
410     Creates a new directory if it doesn't exist, removes
411     existing symlink before creating the directory.
412     """
413     # remove any symlink, if it is there..
414     if os.path.exists(*a) and stat.S_ISLNK(os.lstat(*a).st_mode):
415         LOG.debug('Removing old symlink at %s', *a)
416         os.unlink(*a)
417     try:
418         os.mkdir(*a, **kw)
419     except OSError as e:
420         if e.errno == errno.EEXIST:
421             pass
422         else:
423             raise
424
425
426 def which(executable):
427     """find the location of an executable"""
428     envpath = os.environ.get('PATH') or os.defpath
429     PATH = envpath.split(os.pathsep)
430
431     locations = PATH + [
432         '/usr/local/bin',
433         '/bin',
434         '/usr/bin',
435         '/usr/local/sbin',
436         '/usr/sbin',
437         '/sbin',
438     ]
439
440     for location in locations:
441         executable_path = os.path.join(location, executable)
442         if (os.path.isfile(executable_path) and
443                 os.access(executable_path, os.X_OK)):
444             return executable_path
445
446
447 def _get_command_executable(arguments):
448     """
449     Return the full path for an executable, raise if the executable is not
450     found. If the executable has already a full path do not perform any checks.
451     """
452     if os.path.isabs(arguments[0]):  # an absolute path
453         return arguments
454     executable = which(arguments[0])
455     if not executable:
456         command_msg = 'Could not run command: %s' % ' '.join(arguments)
457         executable_msg = '%s not in path.' % arguments[0]
458         raise ExecutableNotFound('%s %s' % (executable_msg, command_msg))
459
460     # swap the old executable for the new one
461     arguments[0] = executable
462     return arguments
463
464
465 def command(arguments, **kwargs):
466     """
467     Safely execute a ``subprocess.Popen`` call making sure that the
468     executable exists and raising a helpful error message
469     if it does not.
470
471     .. note:: This should be the preferred way of calling ``subprocess.Popen``
472     since it provides the caller with the safety net of making sure that
473     executables *will* be found and will error nicely otherwise.
474
475     This returns the output of the command and the return code of the
476     process in a tuple: (stdout, stderr, returncode).
477     """
478
479     arguments = list(map(_bytes2str, _get_command_executable(arguments)))
480
481     LOG.info('Running command: %s' % ' '.join(arguments))
482     process = subprocess.Popen(
483         arguments,
484         stdout=subprocess.PIPE,
485         stderr=subprocess.PIPE,
486         **kwargs)
487     out, err = process.communicate()
488
489     return _bytes2str(out), _bytes2str(err), process.returncode
490
491
492 def command_with_stdin(arguments, stdin):
493     LOG.info("Running command with stdin: " + " ".join(arguments))
494     process = subprocess.Popen(
495         arguments,
496         stdin=subprocess.PIPE,
497         stdout=subprocess.PIPE,
498         stderr=subprocess.PIPE)
499     out, err = process.communicate(stdin)
500     LOG.debug(out)
501     if process.returncode != 0:
502         LOG.error(err)
503         raise SystemExit(
504             "'{cmd}' failed with status code {returncode}".format(
505                 cmd=arguments,
506                 returncode=process.returncode,
507             )
508         )
509     return out
510
511
512 def _bytes2str(string):
513     return string.decode('utf-8') if isinstance(string, bytes) else string
514
515
516 def command_init(arguments, **kwargs):
517     """
518     Safely execute a non-blocking ``subprocess.Popen`` call
519     making sure that the executable exists and raising a helpful
520     error message if it does not.
521
522     .. note:: This should be the preferred way of calling ``subprocess.Popen``
523     since it provides the caller with the safety net of making sure that
524     executables *will* be found and will error nicely otherwise.
525
526     This returns the process.
527     """
528
529     arguments = list(map(_bytes2str, _get_command_executable(arguments)))
530
531     LOG.info('Running command: %s' % ' '.join(arguments))
532     process = subprocess.Popen(
533         arguments,
534         stdout=subprocess.PIPE,
535         stderr=subprocess.PIPE,
536         **kwargs)
537     return process
538
539
540 def command_wait(process):
541     """
542     Wait for the process finish and parse its output.
543     """
544
545     out, err = process.communicate()
546
547     return _bytes2str(out), _bytes2str(err), process.returncode
548
549
550 def command_check_call(arguments, exit=False):
551     """
552     Safely execute a ``subprocess.check_call`` call making sure that the
553     executable exists and raising a helpful error message if it does not.
554
555     When ``exit`` is set to ``True`` this helper will do a clean (sans
556     traceback) system exit.
557     .. note:: This should be the preferred way of calling
558     ``subprocess.check_call`` since it provides the caller with the safety net
559     of making sure that executables *will* be found and will error nicely
560     otherwise.
561     """
562     arguments = _get_command_executable(arguments)
563     command = ' '.join(arguments)
564     LOG.info('Running command: %s', command)
565     try:
566         return subprocess.check_call(arguments)
567     except subprocess.CalledProcessError as error:
568         if exit:
569             if error.output:
570                 LOG.error(error.output)
571             raise SystemExit(
572                 "'{cmd}' failed with status code {returncode}".format(
573                     cmd=command,
574                     returncode=error.returncode,
575                 )
576             )
577         raise
578
579
580 #
581 # An alternative block_path implementation would be
582 #
583 #   name = basename(dev)
584 #   return /sys/devices/virtual/block/$name
585 #
586 # It is however more fragile because it relies on the fact
587 # that the basename of the device the user will use always
588 # matches the one the driver will use. On Ubuntu 14.04, for
589 # instance, when multipath creates a partition table on
590 #
591 #   /dev/mapper/353333330000007d0 -> ../dm-0
592 #
593 # it will create partition devices named
594 #
595 #   /dev/mapper/353333330000007d0-part1
596 #
597 # which is the same device as /dev/dm-1 but not a symbolic
598 # link to it:
599 #
600 #   ubuntu@other:~$ ls -l /dev/mapper /dev/dm-1
601 #   brw-rw---- 1 root disk 252, 1 Aug 15 17:52 /dev/dm-1
602 #   lrwxrwxrwx 1 root root        7 Aug 15 17:52 353333330000007d0 -> ../dm-0
603 #   brw-rw---- 1 root disk 252,   1 Aug 15 17:52 353333330000007d0-part1
604 #
605 # Using the basename in this case fails.
606 #
607
608
609 def block_path(dev):
610     if FREEBSD:
611         return dev
612     path = os.path.realpath(dev)
613     rdev = os.stat(path).st_rdev
614     (M, m) = (os.major(rdev), os.minor(rdev))
615     return "{sysfs}/dev/block/{M}:{m}".format(sysfs=SYSFS, M=M, m=m)
616
617
618 def get_dm_uuid(dev):
619     uuid_path = os.path.join(block_path(dev), 'dm', 'uuid')
620     LOG.debug("get_dm_uuid " + dev + " uuid path is " + uuid_path)
621     if not os.path.exists(uuid_path):
622         return False
623     uuid = open(uuid_path, 'r').read()
624     LOG.debug("get_dm_uuid " + dev + " uuid is " + uuid)
625     return uuid
626
627
628 def is_mpath(dev):
629     """
630     True if the path is managed by multipath
631     """
632     if FREEBSD:
633         return False
634     uuid = get_dm_uuid(dev)
635     return (uuid and
636             (re.match('part\d+-mpath-', uuid) or
637              re.match('mpath-', uuid)))
638
639
640 def get_dev_name(path):
641     """
642     get device name from path.  e.g.::
643
644         /dev/sda -> sda, /dev/cciss/c0d1 -> cciss!c0d1
645
646     a device "name" is something like::
647
648         sdb
649         cciss!c0d1
650
651     """
652     assert path.startswith('/dev/')
653     base = path[5:]
654     return base.replace('/', '!')
655
656
657 def get_dev_path(name):
658     """
659     get a path (/dev/...) from a name (cciss!c0d1)
660     a device "path" is something like::
661
662         /dev/sdb
663         /dev/cciss/c0d1
664
665     """
666     return '/dev/' + name.replace('!', '/')
667
668
669 def get_dev_relpath(name):
670     """
671     get a relative path to /dev from a name (cciss!c0d1)
672     """
673     return name.replace('!', '/')
674
675
676 def get_dev_size(dev, size='megabytes'):
677     """
678     Attempt to get the size of a device so that we can prevent errors
679     from actions to devices that are smaller, and improve error reporting.
680
681     Because we want to avoid breakage in case this approach is not robust, we
682     will issue a warning if we failed to get the size.
683
684     :param size: bytes or megabytes
685     :param dev: the device to calculate the size
686     """
687     fd = os.open(dev, os.O_RDONLY)
688     dividers = {'bytes': 1, 'megabytes': 1024 * 1024}
689     try:
690         device_size = os.lseek(fd, 0, os.SEEK_END)
691         divider = dividers.get(size, 1024 * 1024)  # default to megabytes
692         return device_size // divider
693     except Exception as error:
694         LOG.warning('failed to get size of %s: %s' % (dev, str(error)))
695     finally:
696         os.close(fd)
697
698
699 def stmode_is_diskdevice(dmode):
700     if stat.S_ISBLK(dmode):
701         return True
702     else:
703         # FreeBSD does not have block devices
704         # All disks are character devices
705         return FREEBSD and stat.S_ISCHR(dmode)
706
707
708 def dev_is_diskdevice(dev):
709     dmode = os.stat(dev).st_mode
710     return stmode_is_diskdevice(dmode)
711
712
713 def ldev_is_diskdevice(dev):
714     dmode = os.lstat(dev).st_mode
715     return stmode_is_diskdevice(dmode)
716
717
718 def path_is_diskdevice(path):
719     dev = os.path.realpath(path)
720     return dev_is_diskdevice(dev)
721
722
723 def get_partition_mpath(dev, pnum):
724     part_re = "part{pnum}-mpath-".format(pnum=pnum)
725     partitions = list_partitions_mpath(dev, part_re)
726     if partitions:
727         return partitions[0]
728     else:
729         return None
730
731
732 def retry(on_error=Exception, max_tries=10, wait=0.2, backoff=0):
733     def wrapper(func):
734         @functools.wraps(func)
735         def repeat(*args, **kwargs):
736             for tries in range(max_tries - 1):
737                 try:
738                     return func(*args, **kwargs)
739                 except on_error:
740                     time.sleep(wait + backoff * tries)
741             return func(*args, **kwargs)
742         return repeat
743     return wrapper
744
745
746 @retry(Error)
747 def get_partition_dev(dev, pnum):
748     """
749     get the device name for a partition
750
751     assume that partitions are named like the base dev,
752     with a number, and optionally
753     some intervening characters (like 'p').  e.g.,
754
755        sda 1 -> sda1
756        cciss/c0d1 1 -> cciss!c0d1p1
757     """
758     partname = None
759     error_msg = ""
760     if is_mpath(dev):
761         partname = get_partition_mpath(dev, pnum)
762     else:
763         name = get_dev_name(os.path.realpath(dev))
764         sys_entry = os.path.join(BLOCKDIR, name)
765         error_msg = " in %s" % sys_entry
766         for f in os.listdir(sys_entry):
767             if f.startswith(name) and f.endswith(str(pnum)):
768                 # we want the shortest name that starts with the base name
769                 # and ends with the partition number
770                 if not partname or len(f) < len(partname):
771                     partname = f
772     if partname:
773         return get_dev_path(partname)
774     else:
775         raise Error('partition %d for %s does not appear to exist%s' %
776                     (pnum, dev, error_msg))
777
778
779 def list_all_partitions():
780     """
781     Return a list of devices and partitions
782     """
783     if not FREEBSD:
784         names = os.listdir(BLOCKDIR)
785         dev_part_list = {}
786         for name in names:
787             # /dev/fd0 may hang http://tracker.ceph.com/issues/6827
788             if re.match(r'^fd\d$', name):
789                 continue
790             dev_part_list[name] = list_partitions(get_dev_path(name))
791     else:
792         with open(os.path.join(PROCDIR, "partitions")) as partitions:
793             for line in partitions:
794                 columns = line.split()
795                 if len(columns) >= 4:
796                     name = columns[3]
797                     dev_part_list[name] = list_partitions(get_dev_path(name))
798     return dev_part_list
799
800
801 def list_partitions(dev):
802     dev = os.path.realpath(dev)
803     if is_mpath(dev):
804         return list_partitions_mpath(dev)
805     else:
806         return list_partitions_device(dev)
807
808
809 def list_partitions_mpath(dev, part_re="part\d+-mpath-"):
810     p = block_path(dev)
811     partitions = []
812     holders = os.path.join(p, 'holders')
813     for holder in os.listdir(holders):
814         uuid_path = os.path.join(holders, holder, 'dm', 'uuid')
815         uuid = open(uuid_path, 'r').read()
816         LOG.debug("list_partitions_mpath: " + uuid_path + " uuid = " + uuid)
817         if re.match(part_re, uuid):
818             partitions.append(holder)
819     return partitions
820
821
822 def list_partitions_device(dev):
823     """
824     Return a list of partitions on the given device name
825     """
826     partitions = []
827     basename = get_dev_name(dev)
828     for name in os.listdir(block_path(dev)):
829         if name.startswith(basename):
830             partitions.append(name)
831     return partitions
832
833
834 def get_partition_base(dev):
835     """
836     Get the base device for a partition
837     """
838     dev = os.path.realpath(dev)
839     if not ldev_is_diskdevice(dev):
840         raise Error('not a block device', dev)
841
842     name = get_dev_name(dev)
843     if os.path.exists(os.path.join('/sys/block', name)):
844         raise Error('not a partition', dev)
845
846     # find the base
847     for basename in os.listdir('/sys/block'):
848         if os.path.exists(os.path.join('/sys/block', basename, name)):
849             return get_dev_path(basename)
850     raise Error('no parent device for partition', dev)
851
852
853 def is_partition_mpath(dev):
854     uuid = get_dm_uuid(dev)
855     return bool(re.match('part\d+-mpath-', uuid))
856
857
858 def partnum_mpath(dev):
859     uuid = get_dm_uuid(dev)
860     return re.findall('part(\d+)-mpath-', uuid)[0]
861
862
863 def get_partition_base_mpath(dev):
864     slave_path = os.path.join(block_path(dev), 'slaves')
865     slaves = os.listdir(slave_path)
866     assert slaves
867     name_path = os.path.join(slave_path, slaves[0], 'dm', 'name')
868     name = open(name_path, 'r').read().strip()
869     return os.path.join('/dev/mapper', name)
870
871
872 def is_partition(dev):
873     """
874     Check whether a given device path is a partition or a full disk.
875     """
876     if is_mpath(dev):
877         return is_partition_mpath(dev)
878
879     dev = os.path.realpath(dev)
880     st = os.lstat(dev)
881     if not stmode_is_diskdevice(st.st_mode):
882         raise Error('not a block device', dev)
883
884     name = get_dev_name(dev)
885     if os.path.exists(os.path.join(BLOCKDIR, name)):
886         return False
887
888     # make sure it is a partition of something else
889     major = os.major(st.st_rdev)
890     minor = os.minor(st.st_rdev)
891     if os.path.exists('/sys/dev/block/%d:%d/partition' % (major, minor)):
892         return True
893
894     raise Error('not a disk or partition', dev)
895
896
897 def is_mounted(dev):
898     """
899     Check if the given device is mounted.
900     """
901     dev = os.path.realpath(dev)
902     with open(PROCDIR + '/mounts', 'rb') as proc_mounts:
903         for line in proc_mounts:
904             fields = line.split()
905             if len(fields) < 3:
906                 continue
907             mounts_dev = fields[0]
908             path = fields[1]
909             if os.path.isabs(mounts_dev) and os.path.exists(mounts_dev):
910                 mounts_dev = os.path.realpath(mounts_dev)
911                 if mounts_dev == dev:
912                     return _bytes2str(path)
913     return None
914
915
916 def is_held(dev):
917     """
918     Check if a device is held by another device (e.g., a dm-crypt mapping)
919     """
920     assert os.path.exists(dev)
921     if is_mpath(dev):
922         return []
923
924     dev = os.path.realpath(dev)
925     base = get_dev_name(dev)
926
927     # full disk?
928     directory = '/sys/block/{base}/holders'.format(base=base)
929     if os.path.exists(directory):
930         return os.listdir(directory)
931
932     # partition?
933     part = base
934     while len(base):
935         directory = '/sys/block/{base}/{part}/holders'.format(
936             part=part, base=base)
937         if os.path.exists(directory):
938             return os.listdir(directory)
939         base = base[:-1]
940     return []
941
942
943 def verify_not_in_use(dev, check_partitions=False):
944     """
945     Verify if a given device (path) is in use (e.g. mounted or
946     in use by device-mapper).
947
948     :raises: Error if device is in use.
949     """
950     assert os.path.exists(dev)
951     if is_mounted(dev):
952         raise Error('Device is mounted', dev)
953     holders = is_held(dev)
954     if holders:
955         raise Error('Device %s is in use by a device-mapper '
956                     'mapping (dm-crypt?)' % dev, ','.join(holders))
957
958     if check_partitions and not is_partition(dev):
959         for partname in list_partitions(dev):
960             partition = get_dev_path(partname)
961             if is_mounted(partition):
962                 raise Error('Device is mounted', partition)
963             holders = is_held(partition)
964             if holders:
965                 raise Error('Device %s is in use by a device-mapper '
966                             'mapping (dm-crypt?)'
967                             % partition, ','.join(holders))
968
969
970 def must_be_one_line(line):
971     """
972     Checks if given line is really one single line.
973
974     :raises: TruncatedLineError or TooManyLinesError
975     :return: Content of the line, or None if line isn't valid.
976     """
977     line = _bytes2str(line)
978
979     if line[-1:] != '\n':
980         raise TruncatedLineError(line)
981     line = line[:-1]
982     if '\n' in line:
983         raise TooManyLinesError(line)
984     return line
985
986
987 def read_one_line(parent, name):
988     """
989     Read a file whose sole contents are a single line.
990
991     Strips the newline.
992
993     :return: Contents of the line, or None if file did not exist.
994     """
995     path = os.path.join(parent, name)
996     try:
997         line = open(path, 'rb').read()
998     except IOError as e:
999         if e.errno == errno.ENOENT:
1000             return None
1001         else:
1002             raise
1003
1004     try:
1005         line = must_be_one_line(line)
1006     except (TruncatedLineError, TooManyLinesError) as e:
1007         raise Error(
1008             'File is corrupt: {path}: {msg}'.format(
1009                 path=path,
1010                 msg=e,
1011             )
1012         )
1013     return line
1014
1015
1016 def write_one_line(parent, name, text):
1017     """
1018     Write a file whose sole contents are a single line.
1019
1020     Adds a newline.
1021     """
1022     path = os.path.join(parent, name)
1023     tmp = '{path}.{pid}.tmp'.format(path=path, pid=os.getpid())
1024     with open(tmp, 'wb') as tmp_file:
1025         tmp_file.write(text.encode('utf-8') + b'\n')
1026         os.fsync(tmp_file.fileno())
1027     path_set_context(tmp)
1028     os.rename(tmp, path)
1029
1030
1031 def init_get():
1032     """
1033     Get a init system using 'ceph-detect-init'
1034     """
1035     init = _check_output(
1036         args=[
1037             'ceph-detect-init',
1038             '--default', 'sysvinit',
1039         ],
1040     )
1041     init = must_be_one_line(init)
1042     return init
1043
1044
1045 def check_osd_magic(path):
1046     """
1047     Check that this path has the Ceph OSD magic.
1048
1049     :raises: BadMagicError if this does not look like a Ceph OSD data
1050     dir.
1051     """
1052     magic = read_one_line(path, 'magic')
1053     if magic is None:
1054         # probably not mkfs'ed yet
1055         raise BadMagicError(path)
1056     if magic != CEPH_OSD_ONDISK_MAGIC:
1057         raise BadMagicError(path)
1058
1059
1060 def check_osd_id(osd_id):
1061     """
1062     Ensures osd id is numeric.
1063     """
1064     if not re.match(r'^[0-9]+$', osd_id):
1065         raise Error('osd id is not numeric', osd_id)
1066
1067
1068 def allocate_osd_id(
1069     cluster,
1070     fsid,
1071     keyring,
1072     path,
1073 ):
1074     """
1075     Allocates an OSD id on the given cluster.
1076
1077     :raises: Error if the call to allocate the OSD id fails.
1078     :return: The allocated OSD id.
1079     """
1080     lockbox_path = os.path.join(STATEDIR, 'osd-lockbox', fsid)
1081     lockbox_osd_id = read_one_line(lockbox_path, 'whoami')
1082     osd_keyring = os.path.join(path, 'keyring')
1083     if lockbox_osd_id:
1084         LOG.debug('Getting OSD id from Lockbox...')
1085         osd_id = lockbox_osd_id
1086         shutil.move(os.path.join(lockbox_path, 'osd_keyring'),
1087                     osd_keyring)
1088         path_set_context(osd_keyring)
1089         os.unlink(os.path.join(lockbox_path, 'whoami'))
1090         return osd_id
1091
1092     LOG.debug('Allocating OSD id...')
1093     secrets = Secrets()
1094     try:
1095         wanttobe = read_one_line(path, 'wanttobe')
1096         if os.path.exists(os.path.join(path, 'wanttobe')):
1097             os.unlink(os.path.join(path, 'wanttobe'))
1098         id_arg = wanttobe and [wanttobe] or []
1099         osd_id = command_with_stdin(
1100             [
1101                 'ceph',
1102                 '--cluster', cluster,
1103                 '--name', 'client.bootstrap-osd',
1104                 '--keyring', keyring,
1105                 '-i', '-',
1106                 'osd', 'new',
1107                 fsid,
1108             ] + id_arg,
1109             secrets.get_json()
1110         )
1111     except subprocess.CalledProcessError as e:
1112         raise Error('ceph osd create failed', e, e.output)
1113     osd_id = must_be_one_line(osd_id)
1114     check_osd_id(osd_id)
1115     secrets.write_osd_keyring(osd_keyring, osd_id)
1116     return osd_id
1117
1118
1119 def get_osd_id(path):
1120     """
1121     Gets the OSD id of the OSD at the given path.
1122     """
1123     osd_id = read_one_line(path, 'whoami')
1124     if osd_id is not None:
1125         check_osd_id(osd_id)
1126     return osd_id
1127
1128
1129 def get_ceph_user():
1130     global CEPH_PREF_USER
1131
1132     if CEPH_PREF_USER is not None:
1133         try:
1134             pwd.getpwnam(CEPH_PREF_USER)
1135             return CEPH_PREF_USER
1136         except KeyError:
1137             print("No such user:", CEPH_PREF_USER)
1138             sys.exit(2)
1139     else:
1140         try:
1141             pwd.getpwnam('ceph')
1142             return 'ceph'
1143         except KeyError:
1144             return 'root'
1145
1146
1147 def get_ceph_group():
1148     global CEPH_PREF_GROUP
1149
1150     if CEPH_PREF_GROUP is not None:
1151         try:
1152             grp.getgrnam(CEPH_PREF_GROUP)
1153             return CEPH_PREF_GROUP
1154         except KeyError:
1155             print("No such group:", CEPH_PREF_GROUP)
1156             sys.exit(2)
1157     else:
1158         try:
1159             grp.getgrnam('ceph')
1160             return 'ceph'
1161         except KeyError:
1162             return 'root'
1163
1164
1165 def path_set_context(path):
1166     # restore selinux context to default policy values
1167     if which('restorecon'):
1168         command(['restorecon', '-R', path])
1169
1170     # if ceph user exists, set owner to ceph
1171     if get_ceph_user() == 'ceph':
1172         command(['chown', '-R', 'ceph:ceph', path])
1173
1174
1175 def _check_output(args=None, **kwargs):
1176     out, err, ret = command(args, **kwargs)
1177     if ret:
1178         cmd = args[0]
1179         error = subprocess.CalledProcessError(ret, cmd)
1180         error.output = out + err
1181         raise error
1182     return _bytes2str(out)
1183
1184
1185 def get_conf(cluster, variable):
1186     """
1187     Get the value of the given configuration variable from the
1188     cluster.
1189
1190     :raises: Error if call to ceph-conf fails.
1191     :return: The variable value or None.
1192     """
1193     try:
1194         out, err, ret = command(
1195             [
1196                 'ceph-conf',
1197                 '--cluster={cluster}'.format(
1198                     cluster=cluster,
1199                 ),
1200                 '--name=osd.',
1201                 '--lookup',
1202                 variable,
1203             ],
1204             close_fds=True,
1205         )
1206     except OSError as e:
1207         raise Error('error executing ceph-conf', e, err)
1208     if ret == 1:
1209         # config entry not found
1210         return None
1211     elif ret != 0:
1212         raise Error('getting variable from configuration failed')
1213     value = out.split('\n', 1)[0]
1214     # don't differentiate between "var=" and no var set
1215     if not value:
1216         return None
1217     return value
1218
1219
1220 def get_conf_with_default(cluster, variable):
1221     """
1222     Get a config value that is known to the C++ code.
1223
1224     This will fail if called on variables that are not defined in
1225     common config options.
1226     """
1227     try:
1228         out = _check_output(
1229             args=[
1230                 'ceph-osd',
1231                 '--cluster={cluster}'.format(
1232                     cluster=cluster,
1233                 ),
1234                 '--show-config-value={variable}'.format(
1235                     variable=variable,
1236                 ),
1237             ],
1238             close_fds=True,
1239         )
1240     except subprocess.CalledProcessError as e:
1241         raise Error(
1242             'getting variable from configuration failed',
1243             e,
1244         )
1245
1246     value = str(out).split('\n', 1)[0]
1247     return value
1248
1249
1250 def get_fsid(cluster):
1251     """
1252     Get the fsid of the cluster.
1253
1254     :return: The fsid or raises Error.
1255     """
1256     fsid = get_conf_with_default(cluster=cluster, variable='fsid')
1257     if fsid is None:
1258         raise Error('getting cluster uuid from configuration failed')
1259     return fsid.lower()
1260
1261
1262 def get_dmcrypt_key_path(
1263     _uuid,
1264     key_dir,
1265     luks
1266 ):
1267     """
1268     Get path to dmcrypt key file.
1269
1270     :return: Path to the dmcrypt key file, callers should check for existence.
1271     """
1272     if luks:
1273         path = os.path.join(key_dir, _uuid + ".luks.key")
1274     else:
1275         path = os.path.join(key_dir, _uuid)
1276
1277     return path
1278
1279
1280 def get_dmcrypt_key(
1281     _uuid,
1282     key_dir,
1283     luks
1284 ):
1285     legacy_path = get_dmcrypt_key_path(_uuid, key_dir, luks)
1286     if os.path.exists(legacy_path):
1287         return (legacy_path,)
1288     path = os.path.join(STATEDIR, 'osd-lockbox', _uuid)
1289     if os.path.exists(path):
1290         mode = get_oneliner(path, 'key-management-mode')
1291         osd_uuid = get_oneliner(path, 'osd-uuid')
1292         ceph_fsid = read_one_line(path, 'ceph_fsid')
1293         if ceph_fsid is None:
1294             LOG.warning("no `ceph_fsid` found falling back to 'ceph' "
1295                         "for cluster name")
1296             cluster = 'ceph'
1297         else:
1298             cluster = find_cluster_by_uuid(ceph_fsid)
1299             if cluster is None:
1300                 raise Error('No cluster conf found in ' + SYSCONFDIR +
1301                             ' with fsid %s' % ceph_fsid)
1302
1303         if mode == KEY_MANAGEMENT_MODE_V1:
1304             key, stderr, ret = command(
1305                 [
1306                     'ceph',
1307                     '--cluster', cluster,
1308                     '--name',
1309                     'client.osd-lockbox.' + osd_uuid,
1310                     '--keyring',
1311                     os.path.join(path, 'keyring'),
1312                     'config-key',
1313                     'get',
1314                     'dm-crypt/osd/' + osd_uuid + '/luks',
1315                 ],
1316             )
1317             LOG.debug("stderr " + stderr)
1318             assert ret == 0
1319             return base64.b64decode(key)
1320         else:
1321             raise Error('unknown key-management-mode ' + str(mode))
1322     raise Error('unable to read dm-crypt key', path, legacy_path)
1323
1324
1325 def _dmcrypt_map(
1326     rawdev,
1327     key,
1328     _uuid,
1329     cryptsetup_parameters,
1330     luks,
1331     format_dev=False,
1332 ):
1333     dev = dmcrypt_is_mapped(_uuid)
1334     if dev:
1335         return dev
1336
1337     if isinstance(key, tuple):
1338         # legacy, before lockbox
1339         assert os.path.exists(key[0])
1340         keypath = key[0]
1341         key = None
1342     else:
1343         keypath = '-'
1344     dev = '/dev/mapper/' + _uuid
1345     luksFormat_args = [
1346         'cryptsetup',
1347         '--batch-mode',
1348         '--key-file',
1349         keypath,
1350         'luksFormat',
1351         rawdev,
1352     ] + cryptsetup_parameters
1353
1354     luksOpen_args = [
1355         'cryptsetup',
1356         '--key-file',
1357         keypath,
1358         'luksOpen',
1359         rawdev,
1360         _uuid,
1361     ]
1362
1363     create_args = [
1364         'cryptsetup',
1365         '--key-file',
1366         keypath,
1367         'create',
1368         _uuid,
1369         rawdev,
1370     ] + cryptsetup_parameters
1371
1372     try:
1373         if luks:
1374             if format_dev:
1375                 command_with_stdin(luksFormat_args, key)
1376             command_with_stdin(luksOpen_args, key)
1377         else:
1378             # Plain mode has no format function, nor any validation
1379             # that the key is correct.
1380             command_with_stdin(create_args, key)
1381         # set proper ownership of mapped device
1382         command_check_call(['chown', 'ceph:ceph', dev])
1383         return dev
1384
1385     except subprocess.CalledProcessError as e:
1386         raise Error('unable to map device', rawdev, e)
1387
1388
1389 @retry(Error, max_tries=10, wait=0.5, backoff=1.0)
1390 def dmcrypt_unmap(_uuid):
1391     if not os.path.exists('/dev/mapper/' + _uuid):
1392         return
1393     try:
1394         command_check_call(['cryptsetup', 'remove', _uuid])
1395     except subprocess.CalledProcessError as e:
1396         raise Error('unable to unmap device', _uuid, e)
1397
1398
1399 def mount(
1400     dev,
1401     fstype,
1402     options,
1403 ):
1404     """
1405     Mounts a device with given filessystem type and
1406     mount options to a tempfile path under /var/lib/ceph/tmp.
1407     """
1408     # sanity check: none of the arguments are None
1409     if dev is None:
1410         raise ValueError('dev may not be None')
1411     if fstype is None:
1412         raise ValueError('fstype may not be None')
1413
1414     # pick best-of-breed mount options based on fs type
1415     if options is None:
1416         options = MOUNT_OPTIONS.get(fstype, '')
1417
1418     myTemp = STATEDIR + '/tmp'
1419     # mkdtemp expect 'dir' to be existing on the system
1420     # Let's be sure it's always the case
1421     if not os.path.exists(myTemp):
1422         os.makedirs(myTemp)
1423
1424     # mount
1425     path = tempfile.mkdtemp(
1426         prefix='mnt.',
1427         dir=myTemp,
1428     )
1429     try:
1430         LOG.debug('Mounting %s on %s with options %s', dev, path, options)
1431         command_check_call(
1432             [
1433                 'mount',
1434                 '-t', fstype,
1435                 '-o', options,
1436                 '--',
1437                 dev,
1438                 path,
1439             ],
1440         )
1441         if which('restorecon'):
1442             command(
1443                 [
1444                     'restorecon',
1445                     path,
1446                 ],
1447             )
1448     except subprocess.CalledProcessError as e:
1449         try:
1450             os.rmdir(path)
1451         except (OSError, IOError):
1452             pass
1453         raise MountError(e)
1454
1455     return path
1456
1457
1458 @retry(UnmountError, max_tries=3, wait=0.5, backoff=1.0)
1459 def unmount(
1460     path,
1461     do_rm=True,
1462 ):
1463     """
1464     Unmount and removes the given mount point.
1465     """
1466     try:
1467         LOG.debug('Unmounting %s', path)
1468         command_check_call(
1469             [
1470                 '/bin/umount',
1471                 '--',
1472                 path,
1473             ],
1474         )
1475     except subprocess.CalledProcessError as e:
1476         raise UnmountError(e)
1477     if not do_rm:
1478         return
1479     os.rmdir(path)
1480
1481
1482 ###########################################
1483
1484 def extract_parted_partition_numbers(partitions):
1485     numbers_as_strings = re.findall('^\d+', partitions, re.MULTILINE)
1486     return map(int, numbers_as_strings)
1487
1488
1489 def get_free_partition_index(dev):
1490     """
1491     Get the next free partition index on a given device.
1492
1493     :return: Index number (> 1 if there is already a partition on the device)
1494     or 1 if there is no partition table.
1495     """
1496     try:
1497         lines = _check_output(
1498             args=[
1499                 'parted',
1500                 '--machine',
1501                 '--',
1502                 dev,
1503                 'print',
1504             ],
1505         )
1506     except subprocess.CalledProcessError as e:
1507         LOG.info('cannot read partition index; assume it '
1508                  'isn\'t present\n (Error: %s)' % e)
1509         return 1
1510
1511     if not lines:
1512         raise Error('parted failed to output anything')
1513     LOG.debug('get_free_partition_index: analyzing ' + lines)
1514     if ('CHS;' not in lines and
1515             'CYL;' not in lines and
1516             'BYT;' not in lines):
1517         raise Error('parted output expected to contain one of ' +
1518                     'CHH; CYL; or BYT; : ' + lines)
1519     if os.path.realpath(dev) not in lines:
1520         raise Error('parted output expected to contain ' + dev + ': ' + lines)
1521     _, partitions = lines.split(os.path.realpath(dev))
1522     partition_numbers = extract_parted_partition_numbers(partitions)
1523     if partition_numbers:
1524         return max(partition_numbers) + 1
1525     else:
1526         return 1
1527
1528
1529 def check_journal_reqs(args):
1530     _, _, allows_journal = command([
1531         'ceph-osd', '--check-allows-journal',
1532         '-i', '0',
1533         '--log-file', '$run_dir/$cluster-osd-check.log',
1534         '--cluster', args.cluster,
1535         '--setuser', get_ceph_user(),
1536         '--setgroup', get_ceph_group(),
1537     ])
1538     _, _, wants_journal = command([
1539         'ceph-osd', '--check-wants-journal',
1540         '-i', '0',
1541         '--log-file', '$run_dir/$cluster-osd-check.log',
1542         '--cluster', args.cluster,
1543         '--setuser', get_ceph_user(),
1544         '--setgroup', get_ceph_group(),
1545     ])
1546     _, _, needs_journal = command([
1547         'ceph-osd', '--check-needs-journal',
1548         '-i', '0',
1549         '--log-file', '$run_dir/$cluster-osd-check.log',
1550         '--cluster', args.cluster,
1551         '--setuser', get_ceph_user(),
1552         '--setgroup', get_ceph_group(),
1553     ])
1554     return (not allows_journal, not wants_journal, not needs_journal)
1555
1556
1557 def update_partition(dev, description):
1558     """
1559     Must be called after modifying a partition table so the kernel
1560     know about the change and fire udev events accordingly. A side
1561     effect of partprobe is to remove partitions and add them again.
1562     The first udevadm settle waits for ongoing udev events to
1563     complete, just in case one of them rely on an existing partition
1564     on dev.  The second udevadm settle guarantees to the caller that
1565     all udev events related to the partition table change have been
1566     processed, i.e. the 95-ceph-osd.rules actions and mode changes,
1567     group changes etc. are complete.
1568     """
1569     LOG.debug('Calling partprobe on %s device %s', description, dev)
1570     partprobe_ok = False
1571     error = 'unknown error'
1572     partprobe = _get_command_executable(['partprobe'])[0]
1573     for i in range(5):
1574         command_check_call(['udevadm', 'settle', '--timeout=600'])
1575         try:
1576             _check_output(['flock', '-s', dev, partprobe, dev])
1577             partprobe_ok = True
1578             break
1579         except subprocess.CalledProcessError as e:
1580             error = e.output
1581             if ('unable to inform the kernel' not in error and
1582                     'Device or resource busy' not in error):
1583                 raise
1584             LOG.debug('partprobe %s failed : %s (ignored, waiting 60s)'
1585                       % (dev, error))
1586             time.sleep(60)
1587     if not partprobe_ok:
1588         raise Error('partprobe %s failed : %s' % (dev, error))
1589     command_check_call(['udevadm', 'settle', '--timeout=600'])
1590
1591
1592 def zap_linux(dev):
1593     try:
1594         # Thoroughly wipe all partitions of any traces of
1595         # Filesystems or OSD Journals
1596         #
1597         # In addition we need to write 10M of data to each partition
1598         # to make sure that after re-creating the same partition
1599         # there is no trace left of any previous Filesystem or OSD
1600         # Journal
1601
1602         LOG.debug('Writing zeros to existing partitions on %s', dev)
1603
1604         for partname in list_partitions(dev):
1605             partition = get_dev_path(partname)
1606             command_check_call(
1607                 [
1608                     'wipefs',
1609                     '--all',
1610                     partition,
1611                 ],
1612             )
1613
1614             command_check_call(
1615                 [
1616                     'dd',
1617                     'if=/dev/zero',
1618                     'of={path}'.format(path=partition),
1619                     'bs=1M',
1620                     'count=10',
1621                 ],
1622             )
1623
1624         LOG.debug('Zapping partition table on %s', dev)
1625
1626         # try to wipe out any GPT partition table backups.  sgdisk
1627         # isn't too thorough.
1628         lba_size = 4096
1629         size = 33 * lba_size
1630         with open(dev, 'wb') as dev_file:
1631             dev_file.seek(-size, os.SEEK_END)
1632             dev_file.write(size * b'\0')
1633
1634         command_check_call(
1635             [
1636                 'sgdisk',
1637                 '--zap-all',
1638                 '--',
1639                 dev,
1640             ],
1641         )
1642         command_check_call(
1643             [
1644                 'sgdisk',
1645                 '--clear',
1646                 '--mbrtogpt',
1647                 '--',
1648                 dev,
1649             ],
1650         )
1651         update_partition(dev, 'zapped')
1652
1653     except subprocess.CalledProcessError as e:
1654         raise Error(e)
1655
1656
1657 def zap_freebsd(dev):
1658     try:
1659         # For FreeBSD we just need to zap the partition.
1660         command_check_call(
1661             [
1662                 'gpart',
1663                 'destroy',
1664                 '-F',
1665                 dev,
1666             ],
1667         )
1668
1669     except subprocess.CalledProcessError as e:
1670         raise Error(e)
1671
1672
1673 def zap(dev):
1674     """
1675     Destroy the partition table and content of a given disk.
1676     """
1677     dev = os.path.realpath(dev)
1678     dmode = os.stat(dev).st_mode
1679     if not stat.S_ISBLK(dmode) or is_partition(dev):
1680         raise Error('not full block device; cannot zap', dev)
1681     if FREEBSD:
1682         zap_freebsd(dev)
1683     else:
1684         zap_linux(dev)
1685
1686
1687 def adjust_symlink(target, path):
1688     create = True
1689     if os.path.lexists(path):
1690         try:
1691             mode = os.lstat(path).st_mode
1692             if stat.S_ISREG(mode):
1693                 LOG.debug('Removing old file %s', path)
1694                 os.unlink(path)
1695             elif stat.S_ISLNK(mode):
1696                 old = os.readlink(path)
1697                 if old != target:
1698                     LOG.debug('Removing old symlink %s -> %s', path, old)
1699                     os.unlink(path)
1700                 else:
1701                     create = False
1702         except:
1703             raise Error('unable to remove (or adjust) old file (symlink)',
1704                         path)
1705     if create:
1706         LOG.debug('Creating symlink %s -> %s', path, target)
1707         try:
1708             os.symlink(target, path)
1709         except:
1710             raise Error('unable to create symlink %s -> %s' % (path, target))
1711
1712
1713 def get_mount_options(cluster, fs_type):
1714     mount_options = get_conf(
1715         cluster,
1716         variable='osd_mount_options_{fstype}'.format(
1717             fstype=fs_type,
1718         ),
1719     )
1720     if mount_options is None:
1721         mount_options = get_conf(
1722             cluster,
1723             variable='osd_fs_mount_options_{fstype}'.format(
1724                 fstype=fs_type,
1725             ),
1726         )
1727     else:
1728         # remove whitespaces
1729         mount_options = "".join(mount_options.split())
1730     return mount_options
1731
1732
1733 class Device(object):
1734
1735     def __init__(self, path, args):
1736         self.args = args
1737         self.path = path
1738         self.dev_size = None
1739         self.partitions = {}
1740         self.ptype_map = None
1741         assert not is_partition(self.path)
1742
1743     def create_partition(self, uuid, name, size=0, num=0):
1744         ptype = self.ptype_tobe_for_name(name)
1745         if num == 0:
1746             num = get_free_partition_index(dev=self.path)
1747         if size > 0:
1748             new = '--new={num}:0:+{size}M'.format(num=num, size=size)
1749             if size > self.get_dev_size():
1750                 LOG.error('refusing to create %s on %s' % (name, self.path))
1751                 LOG.error('%s size (%sM) is bigger than device (%sM)'
1752                           % (name, size, self.get_dev_size()))
1753                 raise Error('%s device size (%sM) is not big enough for %s'
1754                             % (self.path, self.get_dev_size(), name))
1755         else:
1756             new = '--largest-new={num}'.format(num=num)
1757
1758         LOG.debug('Creating %s partition num %d size %d on %s',
1759                   name, num, size, self.path)
1760         command_check_call(
1761             [
1762                 'sgdisk',
1763                 new,
1764                 '--change-name={num}:ceph {name}'.format(num=num, name=name),
1765                 '--partition-guid={num}:{uuid}'.format(num=num, uuid=uuid),
1766                 '--typecode={num}:{uuid}'.format(num=num, uuid=ptype),
1767                 '--mbrtogpt',
1768                 '--',
1769                 self.path,
1770             ],
1771             exit=True
1772         )
1773         update_partition(self.path, 'created')
1774         return num
1775
1776     def ptype_tobe_for_name(self, name):
1777         LOG.debug("name = " + name)
1778         if name == 'data':
1779             name = 'osd'
1780         if name == 'lockbox':
1781             if is_mpath(self.path):
1782                 return PTYPE['mpath']['lockbox']['tobe']
1783             else:
1784                 return PTYPE['regular']['lockbox']['tobe']
1785         if self.ptype_map is None:
1786             partition = DevicePartition.factory(
1787                 path=self.path, dev=None, args=self.args)
1788             self.ptype_map = partition.ptype_map
1789         return self.ptype_map[name]['tobe']
1790
1791     def get_partition(self, num):
1792         if num not in self.partitions:
1793             dev = get_partition_dev(self.path, num)
1794             partition = DevicePartition.factory(
1795                 path=self.path, dev=dev, args=self.args)
1796             partition.set_partition_number(num)
1797             self.partitions[num] = partition
1798         return self.partitions[num]
1799
1800     def get_dev_size(self):
1801         if self.dev_size is None:
1802             self.dev_size = get_dev_size(self.path)
1803         return self.dev_size
1804
1805     @staticmethod
1806     def factory(path, args):
1807         return Device(path, args)
1808
1809
1810 class DevicePartition(object):
1811
1812     def __init__(self, args):
1813         self.args = args
1814         self.num = None
1815         self.rawdev = None
1816         self.dev = None
1817         self.uuid = None
1818         self.ptype_map = None
1819         self.ptype = None
1820         self.set_variables_ptype()
1821
1822     def get_uuid(self):
1823         if self.uuid is None:
1824             self.uuid = get_partition_uuid(self.rawdev)
1825         return self.uuid
1826
1827     def get_ptype(self):
1828         if self.ptype is None:
1829             self.ptype = get_partition_type(self.rawdev)
1830         return self.ptype
1831
1832     def set_partition_number(self, num):
1833         self.num = num
1834
1835     def get_partition_number(self):
1836         return self.num
1837
1838     def set_dev(self, dev):
1839         self.dev = dev
1840         self.rawdev = dev
1841
1842     def get_dev(self):
1843         return self.dev
1844
1845     def get_rawdev(self):
1846         return self.rawdev
1847
1848     def set_variables_ptype(self):
1849         self.ptype_map = PTYPE['regular']
1850
1851     def ptype_for_name(self, name):
1852         return self.ptype_map[name]['ready']
1853
1854     @staticmethod
1855     @retry(OSError)
1856     def factory(path, dev, args):
1857         dmcrypt_type = CryptHelpers.get_dmcrypt_type(args)
1858         if ((path is not None and is_mpath(path)) or
1859                 (dev is not None and is_mpath(dev))):
1860             partition = DevicePartitionMultipath(args)
1861         elif dmcrypt_type == 'luks':
1862             partition = DevicePartitionCryptLuks(args)
1863         elif dmcrypt_type == 'plain':
1864             partition = DevicePartitionCryptPlain(args)
1865         else:
1866             partition = DevicePartition(args)
1867         partition.set_dev(dev)
1868         return partition
1869
1870
1871 class DevicePartitionMultipath(DevicePartition):
1872
1873     def set_variables_ptype(self):
1874         self.ptype_map = PTYPE['mpath']
1875
1876
1877 class DevicePartitionCrypt(DevicePartition):
1878
1879     def __init__(self, args):
1880         super(DevicePartitionCrypt, self).__init__(args)
1881         self.osd_dm_key = None
1882         self.cryptsetup_parameters = CryptHelpers.get_cryptsetup_parameters(
1883             self.args)
1884         self.dmcrypt_type = CryptHelpers.get_dmcrypt_type(self.args)
1885         self.dmcrypt_keysize = CryptHelpers.get_dmcrypt_keysize(self.args)
1886
1887     def setup_crypt(self):
1888         pass
1889
1890     def map(self):
1891         self.setup_crypt()
1892         self.dev = _dmcrypt_map(
1893             rawdev=self.rawdev,
1894             key=self.osd_dm_key,
1895             _uuid=self.get_uuid(),
1896             cryptsetup_parameters=self.cryptsetup_parameters,
1897             luks=self.luks(),
1898             format_dev=True,
1899         )
1900
1901     def unmap(self):
1902         self.setup_crypt()
1903         dmcrypt_unmap(self.get_uuid())
1904         self.dev = self.rawdev
1905
1906     def format(self):
1907         self.setup_crypt()
1908         self.map()
1909
1910
1911 class DevicePartitionCryptPlain(DevicePartitionCrypt):
1912
1913     def luks(self):
1914         return False
1915
1916     def setup_crypt(self):
1917         if self.osd_dm_key is not None:
1918             return
1919
1920         self.cryptsetup_parameters += ['--key-size', str(self.dmcrypt_keysize)]
1921
1922         self.osd_dm_key = get_dmcrypt_key(
1923             self.get_uuid(), self.args.dmcrypt_key_dir,
1924             False)
1925
1926     def set_variables_ptype(self):
1927         self.ptype_map = PTYPE['plain']
1928
1929
1930 class DevicePartitionCryptLuks(DevicePartitionCrypt):
1931
1932     def luks(self):
1933         return True
1934
1935     def setup_crypt(self):
1936         if self.osd_dm_key is not None:
1937             return
1938
1939         if self.dmcrypt_keysize == 1024:
1940             # We don't force this into the cryptsetup_parameters,
1941             # as we want the cryptsetup defaults
1942             # to prevail for the actual LUKS key lengths.
1943             pass
1944         else:
1945             self.cryptsetup_parameters += ['--key-size',
1946                                            str(self.dmcrypt_keysize)]
1947
1948         self.osd_dm_key = get_dmcrypt_key(
1949             self.get_uuid(), self.args.dmcrypt_key_dir,
1950             True)
1951
1952     def set_variables_ptype(self):
1953         self.ptype_map = PTYPE['luks']
1954
1955
1956 class Prepare(object):
1957
1958     def __init__(self, args):
1959         self.args = args
1960
1961     @staticmethod
1962     def parser():
1963         parser = argparse.ArgumentParser(add_help=False)
1964         parser.add_argument(
1965             '--cluster',
1966             metavar='NAME',
1967             default='ceph',
1968             help='cluster name to assign this disk to',
1969         )
1970         parser.add_argument(
1971             '--cluster-uuid',
1972             metavar='UUID',
1973             help='cluster uuid to assign this disk to',
1974         )
1975         parser.add_argument(
1976             '--osd-uuid',
1977             metavar='UUID',
1978             help='unique OSD uuid to assign this disk to',
1979         )
1980         parser.add_argument(
1981             '--osd-id',
1982             metavar='ID',
1983             help='unique OSD id to assign this disk to',
1984         )
1985         parser.add_argument(
1986             '--crush-device-class',
1987             help='crush device class to assign this disk to',
1988         )
1989         parser.add_argument(
1990             '--dmcrypt',
1991             action='store_true', default=None,
1992             help='encrypt DATA and/or JOURNAL devices with dm-crypt',
1993         )
1994         parser.add_argument(
1995             '--dmcrypt-key-dir',
1996             metavar='KEYDIR',
1997             default='/etc/ceph/dmcrypt-keys',
1998             help='directory where dm-crypt keys are stored',
1999         )
2000         parser.add_argument(
2001             '--prepare-key',
2002             metavar='PATH',
2003             help='bootstrap-osd keyring path template (%(default)s)',
2004             default='{statedir}/bootstrap-osd/{cluster}.keyring',
2005             dest='prepare_key_template',
2006         )
2007         parser.add_argument(
2008             '--no-locking',
2009             action='store_true', default=None,
2010             help='let many prepare\'s run in parallel',
2011         )
2012         return parser
2013
2014     @staticmethod
2015     def set_subparser(subparsers):
2016         parents = [
2017             Prepare.parser(),
2018             PrepareData.parser(),
2019             Lockbox.parser(),
2020         ]
2021         parents.extend(PrepareFilestore.parent_parsers())
2022         parents.extend(PrepareBluestore.parent_parsers())
2023         parser = subparsers.add_parser(
2024             'prepare',
2025             parents=parents,
2026             formatter_class=argparse.RawDescriptionHelpFormatter,
2027             description=textwrap.fill(textwrap.dedent("""\
2028             If the --bluestore argument is given, a bluestore objectstore
2029             will be created.  If --filestore is provided, a legacy FileStore
2030             objectstore will be created.  If neither is specified, we default
2031             to BlueStore.
2032
2033             When an entire device is prepared for bluestore, two
2034             partitions are created. The first partition is for metadata,
2035             the second partition is for blocks that contain data.
2036
2037             Unless explicitly specified with --block.db or
2038             --block.wal, the bluestore DB and WAL data is stored on
2039             the main block device. For instance:
2040
2041                 ceph-disk prepare --bluestore /dev/sdc
2042
2043             Will create
2044
2045                 /dev/sdc1 for osd metadata
2046                 /dev/sdc2 for block, db, and wal data (the rest of the disk)
2047
2048
2049             If either --block.db or --block.wal are specified to be
2050             the same whole device, they will be created as partition
2051             three and four respectively. For instance:
2052
2053                 ceph-disk prepare --bluestore \\
2054                       --block.db /dev/sdc \\
2055                       --block.wal /dev/sdc \\
2056                       /dev/sdc
2057
2058             Will create
2059
2060                 /dev/sdc1 for osd metadata
2061                 /dev/sdc2 for block (the rest of the disk)
2062                 /dev/sdc3 for db
2063                 /dev/sdc4 for wal
2064
2065             """)),
2066             help='Prepare a directory or disk for a Ceph OSD',
2067         )
2068         parser.set_defaults(
2069             func=Prepare.main,
2070         )
2071         return parser
2072
2073     def prepare(self):
2074         if self.args.no_locking:
2075             self._prepare()
2076         else:
2077             with prepare_lock:
2078                 self._prepare()
2079
2080     @staticmethod
2081     def factory(args):
2082         if args.bluestore:
2083             return PrepareBluestore(args)
2084         else:
2085             return PrepareFilestore(args)
2086
2087     @staticmethod
2088     def main(args):
2089         Prepare.factory(args).prepare()
2090
2091
2092 class PrepareFilestore(Prepare):
2093
2094     def __init__(self, args):
2095         super(PrepareFilestore, self).__init__(args)
2096         if args.dmcrypt:
2097             self.lockbox = Lockbox(args)
2098         self.data = PrepareFilestoreData(args)
2099         self.journal = PrepareJournal(args)
2100
2101     @staticmethod
2102     def parent_parsers():
2103         return [
2104             PrepareJournal.parser(),
2105         ]
2106
2107     def _prepare(self):
2108         if self.data.args.dmcrypt:
2109             self.lockbox.prepare()
2110         self.data.prepare(self.journal)
2111
2112
2113 class PrepareBluestore(Prepare):
2114
2115     def __init__(self, args):
2116         super(PrepareBluestore, self).__init__(args)
2117         if args.dmcrypt:
2118             self.lockbox = Lockbox(args)
2119         self.data = PrepareBluestoreData(args)
2120         self.block = PrepareBluestoreBlock(args)
2121         self.blockdb = PrepareBluestoreBlockDB(args)
2122         self.blockwal = PrepareBluestoreBlockWAL(args)
2123
2124     @staticmethod
2125     def parser():
2126         parser = argparse.ArgumentParser(add_help=False)
2127         parser.add_argument(
2128             '--bluestore',
2129             dest='bluestore',
2130             action='store_true', default=True,
2131             help='bluestore objectstore',
2132         )
2133         parser.add_argument(
2134             '--filestore',
2135             dest='bluestore',
2136             action='store_false',
2137             help='filestore objectstore',
2138         )
2139         return parser
2140
2141     @staticmethod
2142     def parent_parsers():
2143         return [
2144             PrepareBluestore.parser(),
2145             PrepareBluestoreBlock.parser(),
2146             PrepareBluestoreBlockDB.parser(),
2147             PrepareBluestoreBlockWAL.parser(),
2148         ]
2149
2150     def _prepare(self):
2151         if self.data.args.dmcrypt:
2152             self.lockbox.prepare()
2153         to_prepare_list = []
2154         if getattr(self.data.args, 'block.db'):
2155             to_prepare_list.append(self.blockdb)
2156         if getattr(self.data.args, 'block.wal'):
2157             to_prepare_list.append(self.blockwal)
2158         to_prepare_list.append(self.block)
2159         self.data.prepare(*to_prepare_list)
2160
2161
2162 class Space(object):
2163
2164     NAMES = ('block', 'journal', 'block.db', 'block.wal')
2165
2166
2167 class PrepareSpace(object):
2168
2169     NONE = 0
2170     FILE = 1
2171     DEVICE = 2
2172
2173     def __init__(self, args):
2174         self.args = args
2175         self.set_type()
2176         self.space_size = self.get_space_size()
2177         if getattr(self.args, self.name + '_uuid') is None:
2178             setattr(self.args, self.name + '_uuid', str(uuid.uuid4()))
2179         self.space_symlink = None
2180         self.space_dmcrypt = None
2181
2182     def set_type(self):
2183         name = self.name
2184         args = self.args
2185         if (self.wants_space() and
2186                 dev_is_diskdevice(args.data) and
2187                 not is_partition(args.data) and
2188                 getattr(args, name) is None and
2189                 getattr(args, name + '_file') is None):
2190             LOG.info('Will colocate %s with data on %s',
2191                      name, args.data)
2192             setattr(args, name, args.data)
2193
2194         if getattr(args, name) is None:
2195             if getattr(args, name + '_dev'):
2196                 raise Error('%s is unspecified; not a block device' %
2197                             name.capitalize(), getattr(args, name))
2198             self.type = self.NONE
2199             return
2200
2201         if not os.path.exists(getattr(args, name)):
2202             if getattr(args, name + '_dev'):
2203                 raise Error('%s does not exist; not a block device' %
2204                             name.capitalize(), getattr(args, name))
2205             self.type = self.FILE
2206             return
2207
2208         mode = os.stat(getattr(args, name)).st_mode
2209         if stmode_is_diskdevice(mode):
2210             if getattr(args, name + '_file'):
2211                 raise Error('%s is not a regular file' % name.capitalize,
2212                             getattr(args, name))
2213             self.type = self.DEVICE
2214             return
2215
2216         if stat.S_ISREG(mode):
2217             if getattr(args, name + '_dev'):
2218                 raise Error('%s is not a block device' % name.capitalize,
2219                             getattr(args, name))
2220             self.type = self.FILE
2221             return
2222
2223         raise Error('%s %s is neither a block device nor regular file' %
2224                     (name.capitalize, getattr(args, name)))
2225
2226     def is_none(self):
2227         return self.type == self.NONE
2228
2229     def is_file(self):
2230         return self.type == self.FILE
2231
2232     def is_device(self):
2233         return self.type == self.DEVICE
2234
2235     @staticmethod
2236     def parser(name, positional=True):
2237         parser = argparse.ArgumentParser(add_help=False)
2238         parser.add_argument(
2239             '--%s-uuid' % name,
2240             metavar='UUID',
2241             help='unique uuid to assign to the %s' % name,
2242         )
2243         parser.add_argument(
2244             '--%s-file' % name,
2245             action='store_true', default=None,
2246             help='verify that %s is a file' % name.upper(),
2247         )
2248         parser.add_argument(
2249             '--%s-dev' % name,
2250             action='store_true', default=None,
2251             help='verify that %s is a block device' % name.upper(),
2252         )
2253
2254         if positional:
2255             parser.add_argument(
2256                 name,
2257                 metavar=name.upper(),
2258                 nargs='?',
2259                 help=('path to OSD %s disk block device;' % name +
2260                       ' leave out to store %s in file' % name),
2261             )
2262         return parser
2263
2264     def wants_space(self):
2265         return True
2266
2267     def populate_data_path(self, path):
2268         if self.type == self.DEVICE:
2269             self.populate_data_path_device(path)
2270         elif self.type == self.FILE:
2271             self.populate_data_path_file(path)
2272         elif self.type == self.NONE:
2273             pass
2274         else:
2275             raise Error('unexpected type ', self.type)
2276
2277     def populate_data_path_file(self, path):
2278         space_uuid = self.name + '_uuid'
2279         if getattr(self.args, space_uuid) is not None:
2280             write_one_line(path, space_uuid,
2281                            getattr(self.args, space_uuid))
2282         if self.space_symlink is not None:
2283             adjust_symlink(self.space_symlink,
2284                            os.path.join(path, self.name))
2285
2286     def populate_data_path_device(self, path):
2287         self.populate_data_path_file(path)
2288
2289         if self.space_dmcrypt is not None:
2290             adjust_symlink(self.space_dmcrypt,
2291                            os.path.join(path, self.name + '_dmcrypt'))
2292         else:
2293             try:
2294                 os.unlink(os.path.join(path, self.name + '_dmcrypt'))
2295             except OSError:
2296                 pass
2297
2298     def prepare(self):
2299         if self.type == self.DEVICE:
2300             self.prepare_device()
2301         elif self.type == self.FILE:
2302             self.prepare_file()
2303         elif self.type == self.NONE:
2304             pass
2305         else:
2306             raise Error('unexpected type ', self.type)
2307
2308     def prepare_file(self):
2309         space_filename = getattr(self.args, self.name)
2310         if not os.path.exists(space_filename):
2311             LOG.debug('Creating %s file %s with size 0'
2312                       ' (ceph-osd will resize and allocate)',
2313                       self.name,
2314                       space_filename)
2315             space_file = open(space_filename, 'wb')
2316             space_file.close()
2317             path_set_context(space_filename)
2318
2319         LOG.debug('%s is file %s',
2320                   self.name.capitalize(),
2321                   space_filename)
2322         LOG.warning('OSD will not be hot-swappable if %s is '
2323                     'not the same device as the osd data' %
2324                     self.name)
2325         self.space_symlink = space_filename
2326
2327     def prepare_device(self):
2328         reusing_partition = False
2329
2330         if is_partition(getattr(self.args, self.name)):
2331             LOG.debug('%s %s is a partition',
2332                       self.name.capitalize(), getattr(self.args, self.name))
2333             partition = DevicePartition.factory(
2334                 path=None, dev=getattr(self.args, self.name), args=self.args)
2335             if isinstance(partition, DevicePartitionCrypt):
2336                 raise Error(getattr(self.args, self.name) +
2337                             ' partition already exists'
2338                             ' and --dmcrypt specified')
2339             LOG.warning('OSD will not be hot-swappable' +
2340                         ' if ' + self.name + ' is not' +
2341                         ' the same device as the osd data')
2342             if partition.get_ptype() == partition.ptype_for_name(self.name):
2343                 LOG.debug('%s %s was previously prepared with '
2344                           'ceph-disk. Reusing it.',
2345                           self.name.capitalize(),
2346                           getattr(self.args, self.name))
2347                 reusing_partition = True
2348                 # Read and reuse the partition uuid from this journal's
2349                 # previous life. We reuse the uuid instead of changing it
2350                 # because udev does not reliably notice changes to an
2351                 # existing partition's GUID.  See
2352                 # http://tracker.ceph.com/issues/10146
2353                 setattr(self.args, self.name + '_uuid', partition.get_uuid())
2354                 LOG.debug('Reusing %s with uuid %s',
2355                           self.name,
2356                           getattr(self.args, self.name + '_uuid'))
2357             else:
2358                 LOG.warning('%s %s was not prepared with '
2359                             'ceph-disk. Symlinking directly.',
2360                             self.name.capitalize(),
2361                             getattr(self.args, self.name))
2362                 self.space_symlink = getattr(self.args, self.name)
2363                 return
2364
2365         self.space_symlink = '/dev/disk/by-partuuid/{uuid}'.format(
2366             uuid=getattr(self.args, self.name + '_uuid'))
2367
2368         if self.args.dmcrypt:
2369             self.space_dmcrypt = self.space_symlink
2370             self.space_symlink = '/dev/mapper/{uuid}'.format(
2371                 uuid=getattr(self.args, self.name + '_uuid'))
2372
2373         if reusing_partition:
2374             # confirm that the space_symlink exists. It should since
2375             # this was an active space
2376             # in the past. Continuing otherwise would be futile.
2377             assert os.path.exists(self.space_symlink)
2378             return
2379
2380         num = self.desired_partition_number()
2381
2382         if num == 0:
2383             LOG.warning('OSD will not be hot-swappable if %s '
2384                         'is not the same device as the osd data',
2385                         self.name)
2386
2387         device = Device.factory(getattr(self.args, self.name), self.args)
2388         num = device.create_partition(
2389             uuid=getattr(self.args, self.name + '_uuid'),
2390             name=self.name,
2391             size=self.space_size,
2392             num=num)
2393
2394         partition = device.get_partition(num)
2395
2396         LOG.debug('%s is GPT partition %s',
2397                   self.name.capitalize(),
2398                   self.space_symlink)
2399
2400         if isinstance(partition, DevicePartitionCrypt):
2401             partition.format()
2402             partition.map()
2403
2404         command_check_call(
2405             [
2406                 'sgdisk',
2407                 '--typecode={num}:{uuid}'.format(
2408                     num=num,
2409                     uuid=partition.ptype_for_name(self.name),
2410                 ),
2411                 '--',
2412                 getattr(self.args, self.name),
2413             ],
2414         )
2415         update_partition(getattr(self.args, self.name), 'prepared')
2416
2417         LOG.debug('%s is GPT partition %s',
2418                   self.name.capitalize(),
2419                   self.space_symlink)
2420
2421
2422 class PrepareJournal(PrepareSpace):
2423
2424     def __init__(self, args):
2425         self.name = 'journal'
2426         (self.allows_journal,
2427          self.wants_journal,
2428          self.needs_journal) = check_journal_reqs(args)
2429
2430         if args.journal and not self.allows_journal:
2431             raise Error('journal specified but not allowed by osd backend')
2432
2433         super(PrepareJournal, self).__init__(args)
2434
2435     def wants_space(self):
2436         return self.wants_journal
2437
2438     def get_space_size(self):
2439         return int(get_conf_with_default(
2440             cluster=self.args.cluster,
2441             variable='osd_journal_size',
2442         ))
2443
2444     def desired_partition_number(self):
2445         if self.args.journal == self.args.data:
2446             # we're sharing the disk between osd data and journal;
2447             # make journal be partition number 2
2448             num = 2
2449         else:
2450             num = 0
2451         return num
2452
2453     @staticmethod
2454     def parser():
2455         return PrepareSpace.parser('journal')
2456
2457
2458 class PrepareBluestoreBlock(PrepareSpace):
2459
2460     def __init__(self, args):
2461         self.name = 'block'
2462         super(PrepareBluestoreBlock, self).__init__(args)
2463
2464     def get_space_size(self):
2465         block_size = get_conf(
2466             cluster=self.args.cluster,
2467             variable='bluestore_block_size',
2468         )
2469
2470         if block_size is None:
2471             return 0  # get as much space as possible
2472         else:
2473             return int(block_size) / 1048576  # MB
2474
2475     def desired_partition_number(self):
2476         if self.args.block == self.args.data:
2477             num = 2
2478         else:
2479             num = 0
2480         return num
2481
2482     @staticmethod
2483     def parser():
2484         return PrepareSpace.parser('block')
2485
2486
2487 class PrepareBluestoreBlockDB(PrepareSpace):
2488
2489     def __init__(self, args):
2490         self.name = 'block.db'
2491         super(PrepareBluestoreBlockDB, self).__init__(args)
2492
2493     def get_space_size(self):
2494         block_db_size = get_conf(
2495             cluster=self.args.cluster,
2496             variable='bluestore_block_db_size',
2497         )
2498
2499         if block_db_size is None or int(block_db_size) == 0:
2500             block_size = get_conf(
2501                 cluster=self.args.cluster,
2502                 variable='bluestore_block_size',
2503             )
2504             if block_size is None:
2505                 return 1024  # MB
2506             size = int(block_size) / 100 / 1048576
2507             return max(size, 1024)  # MB
2508         else:
2509             return int(block_db_size) / 1048576  # MB
2510
2511     def desired_partition_number(self):
2512         if getattr(self.args, 'block.db') == self.args.data:
2513             num = 3
2514         else:
2515             num = 0
2516         return num
2517
2518     def wants_space(self):
2519         return False
2520
2521     @staticmethod
2522     def parser():
2523         parser = PrepareSpace.parser('block.db', positional=False)
2524         parser.add_argument(
2525             '--block.db',
2526             metavar='BLOCKDB',
2527             help='path to the device or file for bluestore block.db',
2528         )
2529         return parser
2530
2531
2532 class PrepareBluestoreBlockWAL(PrepareSpace):
2533
2534     def __init__(self, args):
2535         self.name = 'block.wal'
2536         super(PrepareBluestoreBlockWAL, self).__init__(args)
2537
2538     def get_space_size(self):
2539         block_size = get_conf(
2540             cluster=self.args.cluster,
2541             variable='bluestore_block_wal_size',
2542         )
2543
2544         if block_size is None:
2545             return 576  # MB, default value
2546         else:
2547             return int(block_size) / 1048576  # MB
2548
2549     def desired_partition_number(self):
2550         if getattr(self.args, 'block.wal') == self.args.data:
2551             num = 4
2552         else:
2553             num = 0
2554         return num
2555
2556     def wants_space(self):
2557         return False
2558
2559     @staticmethod
2560     def parser():
2561         parser = PrepareSpace.parser('block.wal', positional=False)
2562         parser.add_argument(
2563             '--block.wal',
2564             metavar='BLOCKWAL',
2565             help='path to the device or file for bluestore block.wal',
2566         )
2567         return parser
2568
2569
2570 class CryptHelpers(object):
2571
2572     @staticmethod
2573     def get_cryptsetup_parameters(args):
2574         cryptsetup_parameters_str = get_conf(
2575             cluster=args.cluster,
2576             variable='osd_cryptsetup_parameters',
2577         )
2578         if cryptsetup_parameters_str is None:
2579             return []
2580         else:
2581             return shlex.split(cryptsetup_parameters_str)
2582
2583     @staticmethod
2584     def get_dmcrypt_keysize(args):
2585         dmcrypt_keysize_str = get_conf(
2586             cluster=args.cluster,
2587             variable='osd_dmcrypt_key_size',
2588         )
2589         dmcrypt_type = CryptHelpers.get_dmcrypt_type(args)
2590         if dmcrypt_type == 'luks':
2591             if dmcrypt_keysize_str is None:
2592                 # As LUKS will hash the 'passphrase' in .luks.key
2593                 # into a key, set a large default
2594                 # so if not updated for some time, it is still a
2595                 # reasonable value.
2596                 #
2597                 return 1024
2598             else:
2599                 return int(dmcrypt_keysize_str)
2600         elif dmcrypt_type == 'plain':
2601             if dmcrypt_keysize_str is None:
2602                 # This value is hard-coded in the udev script
2603                 return 256
2604             else:
2605                 LOG.warning('ensure the 95-ceph-osd.rules file has '
2606                             'been copied to /etc/udev/rules.d '
2607                             'and modified to call cryptsetup '
2608                             'with --key-size=%s' % dmcrypt_keysize_str)
2609                 return int(dmcrypt_keysize_str)
2610         else:
2611             return 0
2612
2613     @staticmethod
2614     def get_dmcrypt_type(args):
2615         if hasattr(args, 'dmcrypt') and args.dmcrypt:
2616             dmcrypt_type = get_conf(
2617                 cluster=args.cluster,
2618                 variable='osd_dmcrypt_type',
2619             )
2620
2621             if dmcrypt_type is None or dmcrypt_type == 'luks':
2622                 return 'luks'
2623             elif dmcrypt_type == 'plain':
2624                 return 'plain'
2625             else:
2626                 raise Error('invalid osd_dmcrypt_type parameter '
2627                             '(must be luks or plain): ', dmcrypt_type)
2628         else:
2629             return None
2630
2631
2632 class Secrets(object):
2633
2634     def __init__(self):
2635         secret, stderr, ret = command(['ceph-authtool', '--gen-print-key'])
2636         LOG.debug("stderr " + stderr)
2637         assert ret == 0
2638         self.keys = {
2639             'cephx_secret': secret.strip(),
2640         }
2641
2642     def write_osd_keyring(self, keyring, osd_id):
2643         command_check_call(
2644             [
2645                 'ceph-authtool', keyring,
2646                 '--create-keyring',
2647                 '--name', 'osd.' + str(osd_id),
2648                 '--add-key', self.keys['cephx_secret'],
2649             ])
2650         path_set_context(keyring)
2651
2652     def get_json(self):
2653         return bytearray(json.dumps(self.keys), 'ascii')
2654
2655
2656 class LockboxSecrets(Secrets):
2657
2658     def __init__(self, args):
2659         super(LockboxSecrets, self).__init__()
2660
2661         key_size = CryptHelpers.get_dmcrypt_keysize(args)
2662         key = open('/dev/urandom', 'rb').read(key_size / 8)
2663         base64_key = base64.b64encode(key).decode('ascii')
2664
2665         secret, stderr, ret = command(['ceph-authtool', '--gen-print-key'])
2666         LOG.debug("stderr " + stderr)
2667         assert ret == 0
2668
2669         self.keys.update({
2670             'dmcrypt_key': base64_key,
2671             'cephx_lockbox_secret': secret.strip(),
2672         })
2673
2674     def write_lockbox_keyring(self, path, osd_uuid):
2675         keyring = os.path.join(path, 'keyring')
2676         command_check_call(
2677             [
2678                 'ceph-authtool', keyring,
2679                 '--create-keyring',
2680                 '--name', 'client.osd-lockbox.' + osd_uuid,
2681                 '--add-key', self.keys['cephx_lockbox_secret'],
2682             ])
2683         path_set_context(keyring)
2684
2685
2686 class Lockbox(object):
2687
2688     def __init__(self, args):
2689         self.args = args
2690         self.partition = None
2691         self.device = None
2692
2693         if hasattr(self.args, 'lockbox') and self.args.lockbox is None:
2694             self.args.lockbox = self.args.data
2695
2696     def set_partition(self, partition):
2697         self.partition = partition
2698
2699     @staticmethod
2700     def parser():
2701         parser = argparse.ArgumentParser(add_help=False)
2702         parser.add_argument(
2703             '--lockbox',
2704             help='path to the device to store the lockbox',
2705         )
2706         parser.add_argument(
2707             '--lockbox-uuid',
2708             metavar='UUID',
2709             help='unique lockbox uuid',
2710         )
2711         return parser
2712
2713     def create_partition(self):
2714         self.device = Device.factory(self.args.lockbox, argparse.Namespace())
2715         partition_number = 5
2716         self.device.create_partition(uuid=self.args.lockbox_uuid,
2717                                      name='lockbox',
2718                                      num=partition_number,
2719                                      size=10)  # MB
2720         return self.device.get_partition(partition_number)
2721
2722     def set_or_create_partition(self):
2723         if is_partition(self.args.lockbox):
2724             LOG.debug('OSD lockbox device %s is a partition',
2725                       self.args.lockbox)
2726             self.partition = DevicePartition.factory(
2727                 path=None, dev=self.args.lockbox, args=self.args)
2728             ptype = self.partition.get_ptype()
2729             ready = Ptype.get_ready_by_name('lockbox')
2730             if ptype not in ready:
2731                 LOG.warning('incorrect partition UUID: %s, expected %s'
2732                             % (ptype, str(ready)))
2733         else:
2734             LOG.debug('Creating osd partition on %s',
2735                       self.args.lockbox)
2736             self.partition = self.create_partition()
2737
2738     def create_key(self):
2739         cluster = self.args.cluster
2740         bootstrap = self.args.prepare_key_template.format(cluster=cluster,
2741                                                           statedir=STATEDIR)
2742         path = self.get_mount_point()
2743         secrets = LockboxSecrets(self.args)
2744         id_arg = self.args.osd_id and [self.args.osd_id] or []
2745         osd_id = command_with_stdin(
2746             [
2747                 'ceph',
2748                 '--cluster', cluster,
2749                 '--name', 'client.bootstrap-osd',
2750                 '--keyring', bootstrap,
2751                 '-i', '-',
2752                 'osd', 'new', self.args.osd_uuid,
2753             ] + id_arg,
2754             secrets.get_json()
2755         )
2756         secrets.write_lockbox_keyring(path, self.args.osd_uuid)
2757         osd_id = must_be_one_line(osd_id)
2758         check_osd_id(osd_id)
2759         write_one_line(path, 'whoami', osd_id)
2760         secrets.write_osd_keyring(os.path.join(path, 'osd_keyring'), osd_id)
2761         write_one_line(path, 'key-management-mode', KEY_MANAGEMENT_MODE_V1)
2762
2763     def symlink_spaces(self, path):
2764         target = self.get_mount_point()
2765         for name in Space.NAMES:
2766             if (hasattr(self.args, name + '_uuid') and
2767                     getattr(self.args, name + '_uuid')):
2768                 uuid = getattr(self.args, name + '_uuid')
2769                 symlink = os.path.join(STATEDIR, 'osd-lockbox', uuid)
2770                 adjust_symlink(target, symlink)
2771                 write_one_line(path, name + '-uuid', uuid)
2772
2773     def populate(self):
2774         maybe_mkdir(os.path.join(STATEDIR, 'osd-lockbox'))
2775         args = ['mkfs', '-t', 'ext4', self.partition.get_dev()]
2776         LOG.debug('Creating lockbox fs on %s: ' + str(" ".join(args)))
2777         command_check_call(args)
2778         path = self.get_mount_point()
2779         maybe_mkdir(path)
2780         args = ['mount', '-t', 'ext4', self.partition.get_dev(), path]
2781         LOG.debug('Mounting lockbox ' + str(" ".join(args)))
2782         command_check_call(args)
2783         write_one_line(path, 'osd-uuid', self.args.osd_uuid)
2784         if self.args.cluster_uuid is None:
2785             self.args.cluster_uuid = get_fsid(cluster=self.args.cluster)
2786         write_one_line(path, 'ceph_fsid', self.args.cluster_uuid)
2787         self.create_key()
2788         self.symlink_spaces(path)
2789         write_one_line(path, 'magic', CEPH_LOCKBOX_ONDISK_MAGIC)
2790         if self.device is not None:
2791             command_check_call(
2792                 [
2793                     'sgdisk',
2794                     '--typecode={num}:{uuid}'.format(
2795                         num=self.partition.get_partition_number(),
2796                         uuid=self.partition.ptype_for_name('lockbox'),
2797                     ),
2798                     '--',
2799                     get_partition_base(self.partition.get_dev()),
2800                 ],
2801             )
2802
2803     def get_mount_point(self):
2804         return os.path.join(STATEDIR, 'osd-lockbox', self.args.osd_uuid)
2805
2806     def get_osd_uuid(self):
2807         return self.args.osd_uuid
2808
2809     def activate(self):
2810         path = is_mounted(self.partition.get_dev())
2811         if path:
2812             LOG.info("Lockbox already mounted at " + path)
2813             return
2814
2815         path = tempfile.mkdtemp(
2816             prefix='mnt.',
2817             dir=STATEDIR + '/tmp',
2818         )
2819         args = ['mount', '-t', 'ext4', '-o', 'ro',
2820                 self.partition.get_dev(),
2821                 path]
2822         LOG.debug('Mounting lockbox temporarily ' + str(" ".join(args)))
2823         command_check_call(args)
2824         self.args.osd_uuid = get_oneliner(path, 'osd-uuid')
2825         command_check_call(['umount', path])
2826         LOG.debug('Mounting lockbox readonly ' + str(" ".join(args)))
2827         args = ['mount', '-t', 'ext4', '-o', 'ro',
2828                 self.partition.get_dev(),
2829                 self.get_mount_point()]
2830         command_check_call(args)
2831         for name in Space.NAMES + ('osd',):
2832             uuid_path = os.path.join(self.get_mount_point(), name + '-uuid')
2833             if os.path.exists(uuid_path):
2834                 uuid = get_oneliner(self.get_mount_point(), name + '-uuid')
2835                 dev = os.path.join('/dev/disk/by-partuuid/', uuid.lower())
2836                 args = ['ceph-disk', 'trigger', dev]
2837                 command_check_call(args)
2838
2839     def prepare(self):
2840         verify_not_in_use(self.args.lockbox, check_partitions=True)
2841         self.set_or_create_partition()
2842         self.populate()
2843
2844
2845 class PrepareData(object):
2846
2847     FILE = 1
2848     DEVICE = 2
2849
2850     def __init__(self, args):
2851
2852         self.args = args
2853         self.partition = None
2854         self.set_type()
2855         if self.args.cluster_uuid is None:
2856             self.args.cluster_uuid = get_fsid(cluster=self.args.cluster)
2857
2858         if self.args.osd_uuid is None:
2859             self.args.osd_uuid = str(uuid.uuid4())
2860
2861     def set_type(self):
2862         dmode = os.stat(self.args.data).st_mode
2863
2864         if stat.S_ISDIR(dmode):
2865             self.type = self.FILE
2866         elif stmode_is_diskdevice(dmode):
2867             self.type = self.DEVICE
2868         else:
2869             raise Error('not a dir or block device', self.args.data)
2870
2871     def is_file(self):
2872         return self.type == self.FILE
2873
2874     def is_device(self):
2875         return self.type == self.DEVICE
2876
2877     @staticmethod
2878     def parser():
2879         parser = argparse.ArgumentParser(add_help=False)
2880         parser.add_argument(
2881             '--fs-type',
2882             help='file system type to use (e.g. "ext4")',
2883         )
2884         parser.add_argument(
2885             '--zap-disk',
2886             action='store_true', default=None,
2887             help='destroy the partition table (and content) of a disk',
2888         )
2889         parser.add_argument(
2890             '--data-dir',
2891             action='store_true', default=None,
2892             help='verify that DATA is a dir',
2893         )
2894         parser.add_argument(
2895             '--data-dev',
2896             action='store_true', default=None,
2897             help='verify that DATA is a block device',
2898         )
2899         parser.add_argument(
2900             'data',
2901             metavar='DATA',
2902             help='path to OSD data (a disk block device or directory)',
2903         )
2904         return parser
2905
2906     def populate_data_path_file(self, path, *to_prepare_list):
2907         self.populate_data_path(path, *to_prepare_list)
2908
2909     def populate_data_path(self, path, *to_prepare_list):
2910         if os.path.exists(os.path.join(path, 'magic')):
2911             LOG.debug('Data dir %s already exists', path)
2912             return
2913         else:
2914             LOG.debug('Preparing osd data dir %s', path)
2915
2916         if self.args.osd_uuid is None:
2917             self.args.osd_uuid = str(uuid.uuid4())
2918
2919         write_one_line(path, 'ceph_fsid', self.args.cluster_uuid)
2920         write_one_line(path, 'fsid', self.args.osd_uuid)
2921         if self.args.osd_id:
2922             write_one_line(path, 'wanttobe', self.args.osd_id)
2923         if self.args.crush_device_class:
2924             write_one_line(path, 'crush_device_class',
2925                            self.args.crush_device_class)
2926         write_one_line(path, 'magic', CEPH_OSD_ONDISK_MAGIC)
2927
2928         for to_prepare in to_prepare_list:
2929             to_prepare.populate_data_path(path)
2930
2931     def prepare(self, *to_prepare_list):
2932         if self.type == self.DEVICE:
2933             self.prepare_device(*to_prepare_list)
2934         elif self.type == self.FILE:
2935             self.prepare_file(*to_prepare_list)
2936         else:
2937             raise Error('unexpected type ', self.type)
2938
2939     def prepare_file(self, *to_prepare_list):
2940
2941         if not os.path.exists(self.args.data):
2942             raise Error('data path for directory does not exist',
2943                         self.args.data)
2944
2945         if self.args.data_dev:
2946             raise Error('data path is not a block device', self.args.data)
2947
2948         for to_prepare in to_prepare_list:
2949             to_prepare.prepare()
2950
2951         self.populate_data_path_file(self.args.data, *to_prepare_list)
2952
2953     def sanity_checks(self):
2954         if not os.path.exists(self.args.data):
2955             raise Error('data path for device does not exist',
2956                         self.args.data)
2957         verify_not_in_use(self.args.data,
2958                           check_partitions=not self.args.dmcrypt)
2959
2960     def set_variables(self):
2961         if self.args.fs_type is None:
2962             self.args.fs_type = get_conf(
2963                 cluster=self.args.cluster,
2964                 variable='osd_mkfs_type',
2965             )
2966             if self.args.fs_type is None:
2967                 self.args.fs_type = get_conf(
2968                     cluster=self.args.cluster,
2969                     variable='osd_fs_type',
2970                 )
2971             if self.args.fs_type is None:
2972                 self.args.fs_type = DEFAULT_FS_TYPE
2973
2974         self.mkfs_args = get_conf(
2975             cluster=self.args.cluster,
2976             variable='osd_mkfs_options_{fstype}'.format(
2977                 fstype=self.args.fs_type,
2978             ),
2979         )
2980         if self.mkfs_args is None:
2981             self.mkfs_args = get_conf(
2982                 cluster=self.args.cluster,
2983                 variable='osd_fs_mkfs_options_{fstype}'.format(
2984                     fstype=self.args.fs_type,
2985                 ),
2986             )
2987
2988         self.mount_options = get_mount_options(cluster=self.args.cluster,
2989                                                fs_type=self.args.fs_type)
2990
2991         if self.args.osd_uuid is None:
2992             self.args.osd_uuid = str(uuid.uuid4())
2993
2994     def prepare_device(self, *to_prepare_list):
2995         self.sanity_checks()
2996         self.set_variables()
2997         if self.args.zap_disk is not None:
2998             zap(self.args.data)
2999
3000     def create_data_partition(self):
3001         device = Device.factory(self.args.data, self.args)
3002         partition_number = 1
3003         device.create_partition(uuid=self.args.osd_uuid,
3004                                 name='data',
3005                                 num=partition_number,
3006                                 size=self.get_space_size())
3007         return device.get_partition(partition_number)
3008
3009     def set_data_partition(self):
3010         if is_partition(self.args.data):
3011             LOG.debug('OSD data device %s is a partition',
3012                       self.args.data)
3013             self.partition = DevicePartition.factory(
3014                 path=None, dev=self.args.data, args=self.args)
3015             ptype = self.partition.get_ptype()
3016             ready = Ptype.get_ready_by_name('osd')
3017             if ptype not in ready:
3018                 LOG.warning('incorrect partition UUID: %s, expected %s'
3019                             % (ptype, str(ready)))
3020         else:
3021             LOG.debug('Creating osd partition on %s',
3022                       self.args.data)
3023             self.partition = self.create_data_partition()
3024
3025     def populate_data_path_device(self, *to_prepare_list):
3026         partition = self.partition
3027
3028         if isinstance(partition, DevicePartitionCrypt):
3029             partition.map()
3030
3031         try:
3032             args = [
3033                 'mkfs',
3034                 '-t',
3035                 self.args.fs_type,
3036             ]
3037             if self.mkfs_args is not None:
3038                 args.extend(self.mkfs_args.split())
3039                 if self.args.fs_type == 'xfs':
3040                     args.extend(['-f'])  # always force
3041             else:
3042                 args.extend(MKFS_ARGS.get(self.args.fs_type, []))
3043             args.extend([
3044                 '--',
3045                 partition.get_dev(),
3046             ])
3047             LOG.debug('Creating %s fs on %s',
3048                       self.args.fs_type, partition.get_dev())
3049             command_check_call(args, exit=True)
3050
3051             path = mount(dev=partition.get_dev(),
3052                          fstype=self.args.fs_type,
3053                          options=self.mount_options)
3054
3055             try:
3056                 self.populate_data_path(path, *to_prepare_list)
3057             finally:
3058                 path_set_context(path)
3059                 unmount(path)
3060         finally:
3061             if isinstance(partition, DevicePartitionCrypt):
3062                 partition.unmap()
3063
3064         if not is_partition(self.args.data):
3065             command_check_call(
3066                 [
3067                     'sgdisk',
3068                     '--typecode=%d:%s' % (partition.get_partition_number(),
3069                                           partition.ptype_for_name('osd')),
3070                     '--',
3071                     self.args.data,
3072                 ],
3073                 exit=True,
3074             )
3075             update_partition(self.args.data, 'prepared')
3076             command_check_call(['udevadm', 'trigger',
3077                                 '--action=add',
3078                                 '--sysname-match',
3079                                 os.path.basename(partition.rawdev)])
3080
3081
3082 class PrepareFilestoreData(PrepareData):
3083
3084     def get_space_size(self):
3085         return 0  # get as much space as possible
3086
3087     def prepare_device(self, *to_prepare_list):
3088         super(PrepareFilestoreData, self).prepare_device(*to_prepare_list)
3089         for to_prepare in to_prepare_list:
3090             to_prepare.prepare()
3091         self.set_data_partition()
3092         self.populate_data_path_device(*to_prepare_list)
3093
3094     def populate_data_path(self, path, *to_prepare_list):
3095         super(PrepareFilestoreData, self).populate_data_path(path,
3096                                                              *to_prepare_list)
3097         write_one_line(path, 'type', 'filestore')
3098
3099
3100 class PrepareBluestoreData(PrepareData):
3101
3102     def get_space_size(self):
3103         return 100  # MB
3104
3105     def prepare_device(self, *to_prepare_list):
3106         super(PrepareBluestoreData, self).prepare_device(*to_prepare_list)
3107         self.set_data_partition()
3108         for to_prepare in to_prepare_list:
3109             to_prepare.prepare()
3110         self.populate_data_path_device(*to_prepare_list)
3111
3112     def populate_data_path(self, path, *to_prepare_list):
3113         super(PrepareBluestoreData, self).populate_data_path(path,
3114                                                              *to_prepare_list)
3115         write_one_line(path, 'type', 'bluestore')
3116
3117
3118 def mkfs(
3119     path,
3120     cluster,
3121     osd_id,
3122     fsid,
3123     keyring,
3124 ):
3125     monmap = os.path.join(path, 'activate.monmap')
3126     command_check_call(
3127         [
3128             'ceph',
3129             '--cluster', cluster,
3130             '--name', 'client.bootstrap-osd',
3131             '--keyring', keyring,
3132             'mon', 'getmap', '-o', monmap,
3133         ],
3134     )
3135
3136     osd_type = read_one_line(path, 'type')
3137
3138     if osd_type == 'bluestore':
3139         command_check_call(
3140             [
3141                 'ceph-osd',
3142                 '--cluster', cluster,
3143                 '--mkfs',
3144                 '-i', osd_id,
3145                 '--monmap', monmap,
3146                 '--osd-data', path,
3147                 '--osd-uuid', fsid,
3148                 '--setuser', get_ceph_user(),
3149                 '--setgroup', get_ceph_group(),
3150             ],
3151         )
3152     elif osd_type == 'filestore':
3153         command_check_call(
3154             [
3155                 'ceph-osd',
3156                 '--cluster', cluster,
3157                 '--mkfs',
3158                 '-i', osd_id,
3159                 '--monmap', monmap,
3160                 '--osd-data', path,
3161                 '--osd-journal', os.path.join(path, 'journal'),
3162                 '--osd-uuid', fsid,
3163                 '--setuser', get_ceph_user(),
3164                 '--setgroup', get_ceph_group(),
3165             ],
3166         )
3167     else:
3168         raise Error('unrecognized objectstore type %s' % osd_type)
3169
3170
3171 def get_mount_point(cluster, osd_id):
3172     parent = STATEDIR + '/osd'
3173     return os.path.join(
3174         parent,
3175         '{cluster}-{osd_id}'.format(cluster=cluster, osd_id=osd_id),
3176     )
3177
3178
3179 def move_mount(
3180     dev,
3181     path,
3182     cluster,
3183     osd_id,
3184     fstype,
3185     mount_options,
3186 ):
3187     LOG.debug('Moving mount to final location...')
3188     osd_data = get_mount_point(cluster, osd_id)
3189     maybe_mkdir(osd_data)
3190
3191     # pick best-of-breed mount options based on fs type
3192     if mount_options is None:
3193         mount_options = MOUNT_OPTIONS.get(fstype, '')
3194
3195     # we really want to mount --move, but that is not supported when
3196     # the parent mount is shared, as it is by default on RH, Fedora,
3197     # and probably others.  Also, --bind doesn't properly manipulate
3198     # /etc/mtab, which *still* isn't a symlink to /proc/mounts despite
3199     # this being 2013.  Instead, mount the original device at the final
3200     # location.
3201     command_check_call(
3202         [
3203             '/bin/mount',
3204             '-o',
3205             mount_options,
3206             '--',
3207             dev,
3208             osd_data,
3209         ],
3210     )
3211     command_check_call(
3212         [
3213             '/bin/umount',
3214             '-l',   # lazy, in case someone else is peeking at the
3215                     # wrong moment
3216             '--',
3217             path,
3218         ],
3219     )
3220
3221
3222 #
3223 # For upgrade purposes, to make sure there are no competing units,
3224 # both --runtime unit and the default should be disabled. There can be
3225 # two units at the same time: one with --runtime and another without
3226 # it. If, for any reason (manual or ceph-disk) the two units co-exist
3227 # they will compete with each other.
3228 #
3229 def systemd_disable(
3230     path,
3231     osd_id,
3232 ):
3233     # ensure there is no duplicate ceph-osd@.service
3234     for style in ([], ['--runtime']):
3235         command_check_call(
3236             [
3237                 'systemctl',
3238                 'disable',
3239                 'ceph-osd@{osd_id}'.format(osd_id=osd_id),
3240             ] + style,
3241         )
3242
3243
3244 def systemd_start(
3245     path,
3246     osd_id,
3247 ):
3248     systemd_disable(path, osd_id)
3249     if os.path.ismount(path):
3250         style = ['--runtime']
3251     else:
3252         style = []
3253     command_check_call(
3254         [
3255             'systemctl',
3256             'enable',
3257             'ceph-osd@{osd_id}'.format(osd_id=osd_id),
3258         ] + style,
3259     )
3260     command_check_call(
3261         [
3262             'systemctl',
3263             'start',
3264             'ceph-osd@{osd_id}'.format(osd_id=osd_id),
3265         ],
3266     )
3267
3268
3269 def systemd_stop(
3270     path,
3271     osd_id,
3272 ):
3273     systemd_disable(path, osd_id)
3274     command_check_call(
3275         [
3276             'systemctl',
3277             'stop',
3278             'ceph-osd@{osd_id}'.format(osd_id=osd_id),
3279         ],
3280     )
3281
3282
3283 def start_daemon(
3284     cluster,
3285     osd_id,
3286 ):
3287     LOG.debug('Starting %s osd.%s...', cluster, osd_id)
3288
3289     path = (STATEDIR + '/osd/{cluster}-{osd_id}').format(
3290         cluster=cluster, osd_id=osd_id)
3291
3292     try:
3293         if os.path.exists(os.path.join(path, 'upstart')):
3294             command_check_call(
3295                 [
3296                     '/sbin/initctl',
3297                     # use emit, not start, because start would fail if the
3298                     # instance was already running
3299                     'emit',
3300                     # since the daemon starting doesn't guarantee much about
3301                     # the service being operational anyway, don't bother
3302                     # waiting for it
3303                     '--no-wait',
3304                     '--',
3305                     'ceph-osd',
3306                     'cluster={cluster}'.format(cluster=cluster),
3307                     'id={osd_id}'.format(osd_id=osd_id),
3308                 ],
3309             )
3310         elif os.path.exists(os.path.join(path, 'sysvinit')):
3311             if os.path.exists('/usr/sbin/service'):
3312                 svc = '/usr/sbin/service'
3313             else:
3314                 svc = '/sbin/service'
3315             command_check_call(
3316                 [
3317                     svc,
3318                     'ceph',
3319                     '--cluster',
3320                     '{cluster}'.format(cluster=cluster),
3321                     'start',
3322                     'osd.{osd_id}'.format(osd_id=osd_id),
3323                 ],
3324             )
3325         elif os.path.exists(os.path.join(path, 'systemd')):
3326             systemd_start(path, osd_id)
3327         elif os.path.exists(os.path.join(path, 'openrc')):
3328             base_script = '/etc/init.d/ceph-osd'
3329             osd_script = '{base}.{osd_id}'.format(
3330                 base=base_script,
3331                 osd_id=osd_id
3332             )
3333             if not os.path.exists(osd_script):
3334                 os.symlink(base_script, osd_script)
3335             command_check_call(
3336                 [
3337                     osd_script,
3338                     'start',
3339                 ],
3340             )
3341         elif os.path.exists(os.path.join(path, 'bsdrc')):
3342             command_check_call(
3343                 [
3344                     '/usr/sbin/service', 'ceph', 'start',
3345                     'osd.{osd_id}'.format(osd_id=osd_id),
3346                 ],
3347             )
3348         else:
3349             raise Error('{cluster} osd.{osd_id} '
3350                         'is not tagged with an init system'
3351                         .format(
3352                             cluster=cluster,
3353                             osd_id=osd_id,
3354                         ))
3355     except subprocess.CalledProcessError as e:
3356         raise Error('ceph osd start failed', e)
3357
3358
3359 def stop_daemon(
3360     cluster,
3361     osd_id,
3362 ):
3363     LOG.debug('Stoping %s osd.%s...', cluster, osd_id)
3364
3365     path = (STATEDIR + '/osd/{cluster}-{osd_id}').format(
3366         cluster=cluster, osd_id=osd_id)
3367
3368     try:
3369         if os.path.exists(os.path.join(path, 'upstart')):
3370             command_check_call(
3371                 [
3372                     '/sbin/initctl',
3373                     'stop',
3374                     'ceph-osd',
3375                     'cluster={cluster}'.format(cluster=cluster),
3376                     'id={osd_id}'.format(osd_id=osd_id),
3377                 ],
3378             )
3379         elif os.path.exists(os.path.join(path, 'sysvinit')):
3380             svc = which('service')
3381             command_check_call(
3382                 [
3383                     svc,
3384                     'ceph',
3385                     '--cluster',
3386                     '{cluster}'.format(cluster=cluster),
3387                     'stop',
3388                     'osd.{osd_id}'.format(osd_id=osd_id),
3389                 ],
3390             )
3391         elif os.path.exists(os.path.join(path, 'systemd')):
3392             systemd_stop(path, osd_id)
3393         elif os.path.exists(os.path.join(path, 'openrc')):
3394             command_check_call(
3395                 [
3396                     '/etc/init.d/ceph-osd.{osd_id}'.format(osd_id=osd_id),
3397                     'stop',
3398                 ],
3399             )
3400         elif os.path.exists(os.path.join(path, 'bsdrc')):
3401             command_check_call(
3402                 [
3403                     '/usr/local/etc/rc.d/ceph stop osd.{osd_id}'
3404                     .format(osd_id=osd_id),
3405                 ],
3406             )
3407         else:
3408             raise Error('{cluster} osd.{osd_id} '
3409                         'is not tagged with an init system'
3410                         .format(cluster=cluster, osd_id=osd_id))
3411     except subprocess.CalledProcessError as e:
3412         raise Error('ceph osd stop failed', e)
3413
3414
3415 def detect_fstype(dev):
3416     if FREEBSD:
3417         fstype = _check_output(
3418             args=[
3419                 'fstyp',
3420                 '-u',
3421                 dev,
3422             ],
3423         )
3424     else:
3425         fstype = _check_output(
3426             args=[
3427                 '/sbin/blkid',
3428                 # we don't want stale cached results
3429                 '-p',
3430                 '-s', 'TYPE',
3431                 '-o', 'value',
3432                 '--',
3433                 dev,
3434             ],
3435         )
3436     fstype = must_be_one_line(fstype)
3437     return fstype
3438
3439
3440 def dmcrypt_is_mapped(uuid):
3441     path = os.path.join('/dev/mapper', uuid)
3442     if os.path.exists(path):
3443         return path
3444     else:
3445         return None
3446
3447
3448 def dmcrypt_map(dev, dmcrypt_key_dir):
3449     ptype = get_partition_type(dev)
3450     if ptype in Ptype.get_ready_by_type('plain'):
3451         luks = False
3452         cryptsetup_parameters = ['--key-size', '256']
3453     elif ptype in Ptype.get_ready_by_type('luks'):
3454         luks = True
3455         cryptsetup_parameters = []
3456     else:
3457         raise Error('--dmcrypt called for dev %s with invalid ptype %s'
3458                     % (dev, ptype))
3459     part_uuid = get_partition_uuid(dev)
3460     dmcrypt_key = get_dmcrypt_key(part_uuid, dmcrypt_key_dir, luks)
3461     return _dmcrypt_map(
3462         rawdev=dev,
3463         key=dmcrypt_key,
3464         _uuid=part_uuid,
3465         cryptsetup_parameters=cryptsetup_parameters,
3466         luks=luks,
3467         format_dev=False,
3468     )
3469
3470
3471 def mount_activate(
3472     dev,
3473     activate_key_template,
3474     init,
3475     dmcrypt,
3476     dmcrypt_key_dir,
3477     reactivate=False,
3478 ):
3479
3480     if dmcrypt:
3481         part_uuid = get_partition_uuid(dev)
3482         dev = dmcrypt_map(dev, dmcrypt_key_dir)
3483     try:
3484         fstype = detect_fstype(dev=dev)
3485     except (subprocess.CalledProcessError,
3486             TruncatedLineError,
3487             TooManyLinesError) as e:
3488         raise FilesystemTypeError(
3489             'device {dev}'.format(dev=dev),
3490             e,
3491         )
3492
3493     # TODO always using mount options from cluster=ceph for
3494     # now; see http://tracker.newdream.net/issues/3253
3495     mount_options = get_mount_options(cluster='ceph', fs_type=fstype)
3496
3497     path = mount(dev=dev, fstype=fstype, options=mount_options)
3498
3499     # check if the disk is deactive, change the journal owner, group
3500     # mode for correct user and group.
3501     if os.path.exists(os.path.join(path, 'deactive')):
3502         # logging to syslog will help us easy to know udev triggered failure
3503         if not reactivate:
3504             unmount(path)
3505             # we need to unmap again because dmcrypt map will create again
3506             # on bootup stage (due to deactivate)
3507             if '/dev/mapper/' in dev:
3508                 part_uuid = dev.replace('/dev/mapper/', '')
3509                 dmcrypt_unmap(part_uuid)
3510             LOG.info('OSD deactivated! reactivate with: --reactivate')
3511             raise Error('OSD deactivated! reactivate with: --reactivate')
3512         # flag to activate a deactive osd.
3513         deactive = True
3514     else:
3515         deactive = False
3516
3517     osd_id = None
3518     cluster = None
3519     try:
3520         (osd_id, cluster) = activate(path, activate_key_template, init)
3521
3522         # Now active successfully
3523         # If we got reactivate and deactive, remove the deactive file
3524         if deactive and reactivate:
3525             os.remove(os.path.join(path, 'deactive'))
3526             LOG.info('Remove `deactive` file.')
3527
3528         # check if the disk is already active, or if something else is already
3529         # mounted there
3530         active = False
3531         other = False
3532         src_dev = os.stat(path).st_dev
3533         try:
3534             dst_dev = os.stat((STATEDIR + '/osd/{cluster}-{osd_id}').format(
3535                 cluster=cluster,
3536                 osd_id=osd_id)).st_dev
3537             if src_dev == dst_dev:
3538                 active = True
3539             else:
3540                 parent_dev = os.stat(STATEDIR + '/osd').st_dev
3541                 if dst_dev != parent_dev:
3542                     other = True
3543                 elif os.listdir(get_mount_point(cluster, osd_id)):
3544                     LOG.info(get_mount_point(cluster, osd_id) +
3545                              " is not empty, won't override")
3546                     other = True
3547
3548         except OSError:
3549             pass
3550
3551         if active:
3552             LOG.info('%s osd.%s already mounted in position; unmounting ours.'
3553                      % (cluster, osd_id))
3554             unmount(path)
3555         elif other:
3556             raise Error('another %s osd.%s already mounted in position '
3557                         '(old/different cluster instance?); unmounting ours.'
3558                         % (cluster, osd_id))
3559         else:
3560             move_mount(
3561                 dev=dev,
3562                 path=path,
3563                 cluster=cluster,
3564                 osd_id=osd_id,
3565                 fstype=fstype,
3566                 mount_options=mount_options,
3567             )
3568         return cluster, osd_id
3569
3570     except:
3571         LOG.error('Failed to activate')
3572         unmount(path)
3573         raise
3574     finally:
3575         # remove our temp dir
3576         if os.path.exists(path):
3577             os.rmdir(path)
3578
3579
3580 def activate_dir(
3581     path,
3582     activate_key_template,
3583     init,
3584 ):
3585
3586     if not os.path.exists(path):
3587         raise Error(
3588             'directory %s does not exist' % path
3589         )
3590
3591     (osd_id, cluster) = activate(path, activate_key_template, init)
3592
3593     if init not in (None, 'none'):
3594         canonical = (STATEDIR + '/osd/{cluster}-{osd_id}').format(
3595             cluster=cluster,
3596             osd_id=osd_id)
3597         if path != canonical:
3598             # symlink it from the proper location
3599             create = True
3600             if os.path.lexists(canonical):
3601                 old = os.readlink(canonical)
3602                 if old != path:
3603                     LOG.debug('Removing old symlink %s -> %s', canonical, old)
3604                     try:
3605                         os.unlink(canonical)
3606                     except:
3607                         raise Error('unable to remove old symlink', canonical)
3608                 else:
3609                     create = False
3610             if create:
3611                 LOG.debug('Creating symlink %s -> %s', canonical, path)
3612                 try:
3613                     os.symlink(path, canonical)
3614                 except:
3615                     raise Error('unable to create symlink %s -> %s'
3616                                 % (canonical, path))
3617
3618     return cluster, osd_id
3619
3620
3621 def find_cluster_by_uuid(_uuid):
3622     """
3623     Find a cluster name by searching /etc/ceph/*.conf for a conf file
3624     with the right uuid.
3625     """
3626     _uuid = _uuid.lower()
3627     no_fsid = []
3628     if not os.path.exists(SYSCONFDIR):
3629         return None
3630     for conf_file in os.listdir(SYSCONFDIR):
3631         if not conf_file.endswith('.conf'):
3632             continue
3633         cluster = conf_file[:-5]
3634         try:
3635             fsid = get_fsid(cluster)
3636         except Error as e:
3637             if 'getting cluster uuid from configuration failed' not in str(e):
3638                 raise e
3639             no_fsid.append(cluster)
3640         else:
3641             if fsid == _uuid:
3642                 return cluster
3643     # be tolerant of /etc/ceph/ceph.conf without an fsid defined.
3644     if len(no_fsid) == 1 and no_fsid[0] == 'ceph':
3645         LOG.warning('No fsid defined in ' + SYSCONFDIR +
3646                     '/ceph.conf; using anyway')
3647         return 'ceph'
3648     return None
3649
3650
3651 def activate(
3652     path,
3653     activate_key_template,
3654     init,
3655 ):
3656
3657     check_osd_magic(path)
3658
3659     ceph_fsid = read_one_line(path, 'ceph_fsid')
3660     if ceph_fsid is None:
3661         raise Error('No cluster uuid assigned.')
3662     LOG.debug('Cluster uuid is %s', ceph_fsid)
3663
3664     cluster = find_cluster_by_uuid(ceph_fsid)
3665     if cluster is None:
3666         raise Error('No cluster conf found in ' + SYSCONFDIR +
3667                     ' with fsid %s' % ceph_fsid)
3668     LOG.debug('Cluster name is %s', cluster)
3669
3670     fsid = read_one_line(path, 'fsid')
3671     if fsid is None:
3672         raise Error('No OSD uuid assigned.')
3673     LOG.debug('OSD uuid is %s', fsid)
3674
3675     keyring = activate_key_template.format(cluster=cluster,
3676                                            statedir=STATEDIR)
3677
3678     osd_id = get_osd_id(path)
3679     if osd_id is None:
3680         osd_id = allocate_osd_id(
3681             cluster=cluster,
3682             fsid=fsid,
3683             keyring=keyring,
3684             path=path,
3685         )
3686         write_one_line(path, 'whoami', osd_id)
3687     LOG.debug('OSD id is %s', osd_id)
3688
3689     if not os.path.exists(os.path.join(path, 'ready')):
3690         LOG.debug('Initializing OSD...')
3691         # re-running mkfs is safe, so just run until it completes
3692         mkfs(
3693             path=path,
3694             cluster=cluster,
3695             osd_id=osd_id,
3696             fsid=fsid,
3697             keyring=keyring,
3698         )
3699
3700     if init not in (None, 'none'):
3701         if init == 'auto':
3702             conf_val = get_conf(
3703                 cluster=cluster,
3704                 variable='init'
3705             )
3706             if conf_val is not None:
3707                 init = conf_val
3708             else:
3709                 init = init_get()
3710
3711         LOG.debug('Marking with init system %s', init)
3712         init_path = os.path.join(path, init)
3713         with open(init_path, 'w'):
3714             path_set_context(init_path)
3715
3716     # remove markers for others, just in case.
3717     for other in INIT_SYSTEMS:
3718         if other != init:
3719             try:
3720                 os.unlink(os.path.join(path, other))
3721             except OSError:
3722                 pass
3723
3724     if not os.path.exists(os.path.join(path, 'active')):
3725         write_one_line(path, 'active', 'ok')
3726     LOG.debug('%s osd.%s data dir is ready at %s', cluster, osd_id, path)
3727     return (osd_id, cluster)
3728
3729
3730 def main_activate(args):
3731     cluster = None
3732     osd_id = None
3733
3734     LOG.info('path = ' + str(args.path))
3735     if not os.path.exists(args.path):
3736         raise Error('%s does not exist' % args.path)
3737
3738     if is_suppressed(args.path):
3739         LOG.info('suppressed activate request on %s', args.path)
3740         return
3741
3742     with activate_lock:
3743         mode = os.stat(args.path).st_mode
3744         if stmode_is_diskdevice(mode):
3745             if (is_partition(args.path) and
3746                     (get_partition_type(args.path) ==
3747                      PTYPE['mpath']['osd']['ready']) and
3748                     not is_mpath(args.path)):
3749                 raise Error('%s is not a multipath block device' %
3750                             args.path)
3751             (cluster, osd_id) = mount_activate(
3752                 dev=args.path,
3753                 activate_key_template=args.activate_key_template,
3754                 init=args.mark_init,
3755                 dmcrypt=args.dmcrypt,
3756                 dmcrypt_key_dir=args.dmcrypt_key_dir,
3757                 reactivate=args.reactivate,
3758             )
3759             osd_data = get_mount_point(cluster, osd_id)
3760
3761             args.cluster = cluster
3762             if args.dmcrypt:
3763                 for name in Space.NAMES:
3764                     # Check if encrypted device in journal
3765                     dev_path = os.path.join(osd_data, name + '_dmcrypt')
3766                     if not os.path.exists(dev_path):
3767                         continue
3768                     partition = DevicePartition.factory(
3769                         path=None,
3770                         dev=dev_path,
3771                         args=args)
3772                     partition.rawdev = args.path
3773                     partition.map()
3774
3775         elif stat.S_ISDIR(mode):
3776             (cluster, osd_id) = activate_dir(
3777                 path=args.path,
3778                 activate_key_template=args.activate_key_template,
3779                 init=args.mark_init,
3780             )
3781             osd_data = args.path
3782
3783         else:
3784             raise Error('%s is not a directory or block device' % args.path)
3785
3786         # exit with 0 if the journal device is not up, yet
3787         # journal device will do the activation
3788         osd_journal = '{path}/journal'.format(path=osd_data)
3789         if os.path.islink(osd_journal) and not os.access(osd_journal, os.F_OK):
3790             LOG.info("activate: Journal not present, not starting, yet")
3791             return
3792
3793         if (not args.no_start_daemon and args.mark_init == 'none'):
3794             command_check_call(
3795                 [
3796                     'ceph-osd',
3797                     '--cluster={cluster}'.format(cluster=cluster),
3798                     '--id={osd_id}'.format(osd_id=osd_id),
3799                     '--osd-data={path}'.format(path=osd_data),
3800                     '--osd-journal={journal}'.format(journal=osd_journal),
3801                 ],
3802             )
3803
3804         if (not args.no_start_daemon and
3805                 args.mark_init not in (None, 'none')):
3806
3807             start_daemon(
3808                 cluster=cluster,
3809                 osd_id=osd_id,
3810             )
3811
3812
3813 def main_activate_lockbox(args):
3814     with activate_lock:
3815         main_activate_lockbox_protected(args)
3816
3817
3818 def main_activate_lockbox_protected(args):
3819     partition = DevicePartition.factory(
3820         path=None, dev=args.path, args=args)
3821
3822     lockbox = Lockbox(args)
3823     lockbox.set_partition(partition)
3824     lockbox.activate()
3825
3826
3827 ###########################
3828
3829 def _mark_osd_out(cluster, osd_id):
3830     LOG.info('Prepare to mark osd.%d out...', osd_id)
3831     command([
3832         'ceph',
3833         'osd',
3834         'out',
3835         'osd.%d' % osd_id,
3836     ])
3837
3838
3839 def _check_osd_status(cluster, osd_id):
3840     """
3841     report the osd status:
3842     00(0) : means OSD OUT AND DOWN
3843     01(1) : means OSD OUT AND UP
3844     10(2) : means OSD IN AND DOWN
3845     11(3) : means OSD IN AND UP
3846     """
3847     LOG.info("Checking osd id: %s ..." % osd_id)
3848     found = False
3849     status_code = 0
3850     out, err, ret = command([
3851         'ceph',
3852         'osd',
3853         'dump',
3854         '--cluster={cluster}'.format(
3855             cluster=cluster,
3856         ),
3857         '--format',
3858         'json',
3859     ])
3860     out_json = json.loads(out)
3861     for item in out_json[u'osds']:
3862         if item.get(u'osd') == int(osd_id):
3863             found = True
3864             if item.get(u'in') is 1:
3865                 status_code += 2
3866             if item.get(u'up') is 1:
3867                 status_code += 1
3868     if not found:
3869         raise Error('Could not osd.%s in osd tree!' % osd_id)
3870     return status_code
3871
3872
3873 def _remove_osd_directory_files(mounted_path, cluster):
3874     """
3875     To remove the 'ready', 'active', INIT-specific files.
3876     """
3877     if os.path.exists(os.path.join(mounted_path, 'ready')):
3878         os.remove(os.path.join(mounted_path, 'ready'))
3879         LOG.info('Remove `ready` file.')
3880     else:
3881         LOG.info('`ready` file is already removed.')
3882
3883     if os.path.exists(os.path.join(mounted_path, 'active')):
3884         os.remove(os.path.join(mounted_path, 'active'))
3885         LOG.info('Remove `active` file.')
3886     else:
3887         LOG.info('`active` file is already removed.')
3888
3889     # Just check `upstart` and `sysvinit` directly if filename is init-spec.
3890     conf_val = get_conf(
3891         cluster=cluster,
3892         variable='init'
3893     )
3894     if conf_val is not None:
3895         init = conf_val
3896     else:
3897         init = init_get()
3898     os.remove(os.path.join(mounted_path, init))
3899     LOG.info('Remove `%s` file.', init)
3900     return
3901
3902
3903 def main_deactivate(args):
3904     with activate_lock:
3905         main_deactivate_locked(args)
3906
3907
3908 def main_deactivate_locked(args):
3909     osd_id = args.deactivate_by_id
3910     path = args.path
3911     target_dev = None
3912     dmcrypt = False
3913     devices = list_devices()
3914
3915     # list all devices and found we need
3916     for device in devices:
3917         if 'partitions' in device:
3918             for dev_part in device.get('partitions'):
3919                 if (osd_id and
3920                         'whoami' in dev_part and
3921                         dev_part['whoami'] == osd_id):
3922                     target_dev = dev_part
3923                 elif (path and
3924                         'path' in dev_part and
3925                         dev_part['path'] == path):
3926                     target_dev = dev_part
3927     if not target_dev:
3928         raise Error('Cannot find any match device!!')
3929
3930     # set up all we need variable
3931     osd_id = target_dev['whoami']
3932     part_type = target_dev['ptype']
3933     mounted_path = target_dev['mount']
3934     if Ptype.is_dmcrypt(part_type, 'osd'):
3935         dmcrypt = True
3936
3937     # Do not do anything if osd is already down.
3938     status_code = _check_osd_status(args.cluster, osd_id)
3939     if status_code == OSD_STATUS_IN_UP:
3940         if args.mark_out is True:
3941             _mark_osd_out(args.cluster, int(osd_id))
3942         stop_daemon(args.cluster, osd_id)
3943     elif status_code == OSD_STATUS_IN_DOWN:
3944         if args.mark_out is True:
3945             _mark_osd_out(args.cluster, int(osd_id))
3946         LOG.info("OSD already out/down. Do not do anything now.")
3947         return
3948     elif status_code == OSD_STATUS_OUT_UP:
3949         stop_daemon(args.cluster, osd_id)
3950     elif status_code == OSD_STATUS_OUT_DOWN:
3951         LOG.info("OSD already out/down. Do not do anything now.")
3952         return
3953
3954     if not args.once:
3955         # remove 'ready', 'active', and INIT-specific files.
3956         _remove_osd_directory_files(mounted_path, args.cluster)
3957
3958         # Write deactivate to osd directory!
3959         with open(os.path.join(mounted_path, 'deactive'), 'w'):
3960             path_set_context(os.path.join(mounted_path, 'deactive'))
3961
3962     unmount(mounted_path, do_rm=not args.once)
3963     LOG.info("Umount `%s` successfully.", mounted_path)
3964
3965     if dmcrypt:
3966         lockbox = os.path.join(STATEDIR, 'osd-lockbox')
3967         command(['umount', os.path.join(lockbox, target_dev['uuid'])])
3968
3969         dmcrypt_unmap(target_dev['uuid'])
3970         for name in Space.NAMES:
3971             if name + '_uuid' in target_dev:
3972                 dmcrypt_unmap(target_dev[name + '_uuid'])
3973
3974 ###########################
3975
3976
3977 def _remove_lockbox(uuid, cluster):
3978     lockbox = os.path.join(STATEDIR, 'osd-lockbox')
3979     if not os.path.exists(lockbox):
3980         return
3981     canonical = os.path.join(lockbox, uuid)
3982     command(['umount', canonical])
3983     for name in os.listdir(lockbox):
3984         path = os.path.join(lockbox, name)
3985         if os.path.islink(path) and os.readlink(path) == canonical:
3986             os.unlink(path)
3987
3988
3989 def destroy_lookup_device(args, predicate, description):
3990     devices = list_devices()
3991     for device in devices:
3992         for partition in device.get('partitions', []):
3993             if partition['type'] == 'lockbox':
3994                 if not is_mounted(partition['path']):
3995                     main_activate_lockbox_protected(
3996                         argparse.Namespace(verbose=args.verbose,
3997                                            path=partition['path']))
3998     for device in devices:
3999         for partition in device.get('partitions', []):
4000             if partition['dmcrypt']:
4001                 dmcrypt_path = dmcrypt_is_mapped(partition['uuid'])
4002                 if dmcrypt_path:
4003                     unmap = False
4004                 else:
4005                     dmcrypt_path = dmcrypt_map(partition['path'],
4006                                                args.dmcrypt_key_dir)
4007                     unmap = True
4008                 list_dev_osd(dmcrypt_path, {}, partition)
4009                 if unmap:
4010                     dmcrypt_unmap(partition['uuid'])
4011                 dmcrypt = True
4012             else:
4013                 dmcrypt = False
4014             if predicate(partition):
4015                 return dmcrypt, partition
4016     raise Error('found no device matching ', description)
4017
4018
4019 def main_destroy(args):
4020     with activate_lock:
4021         main_destroy_locked(args)
4022
4023
4024 def main_destroy_locked(args):
4025     osd_id = args.destroy_by_id
4026     path = args.path
4027     target_dev = None
4028
4029     if path:
4030         if not is_partition(path):
4031             raise Error(path + " must be a partition device")
4032         path = os.path.realpath(path)
4033
4034     if path:
4035         (dmcrypt, target_dev) = destroy_lookup_device(
4036             args, lambda x: x.get('path') == path,
4037             path)
4038     elif osd_id:
4039         (dmcrypt, target_dev) = destroy_lookup_device(
4040             args, lambda x: x.get('whoami') == osd_id,
4041             'osd id ' + str(osd_id))
4042
4043     osd_id = target_dev['whoami']
4044     dev_path = target_dev['path']
4045     if target_dev['ptype'] == PTYPE['mpath']['osd']['ready']:
4046         base_dev = get_partition_base_mpath(dev_path)
4047     else:
4048         base_dev = get_partition_base(dev_path)
4049
4050     # Before osd deactivate, we cannot destroy it
4051     status_code = _check_osd_status(args.cluster, osd_id)
4052     if status_code != OSD_STATUS_OUT_DOWN and \
4053        status_code != OSD_STATUS_IN_DOWN:
4054         raise Error("Could not destroy the active osd. (osd-id: %s)" %
4055                     osd_id)
4056
4057     if args.purge:
4058         action = 'purge'
4059     else:
4060         action = 'destroy'
4061     LOG.info("Prepare to %s osd.%s" % (action, osd_id))
4062     command([
4063         'ceph',
4064         'osd',
4065         action,
4066         'osd.%s' % osd_id,
4067         '--yes-i-really-mean-it',
4068     ])
4069
4070     # we remove the crypt map and device mapper (if dmcrypt is True)
4071     if dmcrypt:
4072         for name in Space.NAMES:
4073             if target_dev.get(name + '_uuid'):
4074                 dmcrypt_unmap(target_dev[name + '_uuid'])
4075         _remove_lockbox(target_dev['uuid'], args.cluster)
4076
4077     # Check zap flag. If we found zap flag, we need to find device for
4078     # destroy this osd data.
4079     if args.zap is True:
4080         # erase the osd data
4081         LOG.info("Prepare to zap the device %s" % base_dev)
4082         zap(base_dev)
4083
4084
4085 def get_space_osd_uuid(name, path):
4086     if not os.path.exists(path):
4087         raise Error('%s does not exist' % path)
4088
4089     if not path_is_diskdevice(path):
4090         raise Error('%s is not a block device' % path)
4091
4092     if (is_partition(path) and
4093             get_partition_type(path) in (PTYPE['mpath']['journal']['ready'],
4094                                          PTYPE['mpath']['block']['ready']) and
4095             not is_mpath(path)):
4096         raise Error('%s is not a multipath block device' %
4097                     path)
4098
4099     try:
4100         out = _check_output(
4101             args=[
4102                 'ceph-osd',
4103                 '--get-device-fsid',
4104                 path,
4105             ],
4106             close_fds=True,
4107         )
4108     except subprocess.CalledProcessError as e:
4109         raise Error(
4110             'failed to get osd uuid/fsid from %s' % name,
4111             e,
4112         )
4113     value = str(out).split('\n', 1)[0]
4114     LOG.debug('%s %s has OSD UUID %s', name.capitalize(), path, value)
4115     return value
4116
4117
4118 def main_activate_space(name, args):
4119     if not os.path.exists(args.dev):
4120         raise Error('%s does not exist' % args.dev)
4121
4122     if is_suppressed(args.dev):
4123         LOG.info('suppressed activate request on space %s', args.dev)
4124         return
4125
4126     cluster = None
4127     osd_id = None
4128     osd_uuid = None
4129     dev = None
4130     with activate_lock:
4131         if args.dmcrypt:
4132             dev = dmcrypt_map(args.dev, args.dmcrypt_key_dir)
4133         else:
4134             dev = args.dev
4135         # FIXME: For an encrypted journal dev, does this return the
4136         # cyphertext or plaintext dev uuid!? Also, if the journal is
4137         # encrypted, is the data partition also always encrypted, or
4138         # are mixed pairs supported!?
4139         osd_uuid = get_space_osd_uuid(name, dev)
4140         path = os.path.join('/dev/disk/by-partuuid/', osd_uuid.lower())
4141
4142         if is_suppressed(path):
4143             LOG.info('suppressed activate request on %s', path)
4144             return
4145
4146         # warn and exit with 0 if the data device is not up, yet
4147         # data device will do the activation
4148         if not os.access(path, os.F_OK):
4149             LOG.info("activate: OSD device not present, not starting, yet")
4150             return
4151
4152         (cluster, osd_id) = mount_activate(
4153             dev=path,
4154             activate_key_template=args.activate_key_template,
4155             init=args.mark_init,
4156             dmcrypt=args.dmcrypt,
4157             dmcrypt_key_dir=args.dmcrypt_key_dir,
4158             reactivate=args.reactivate,
4159         )
4160
4161         start_daemon(
4162             cluster=cluster,
4163             osd_id=osd_id,
4164         )
4165
4166
4167 ###########################
4168
4169
4170 def main_activate_all(args):
4171     dir = '/dev/disk/by-parttypeuuid'
4172     LOG.debug('Scanning %s', dir)
4173     if not os.path.exists(dir):
4174         return
4175     err = False
4176     for name in os.listdir(dir):
4177         if name.find('.') < 0:
4178             continue
4179         (tag, uuid) = name.split('.')
4180
4181         if tag in Ptype.get_ready_by_name('osd'):
4182
4183             if Ptype.is_dmcrypt(tag, 'osd'):
4184                 path = os.path.join('/dev/mapper', uuid)
4185             else:
4186                 path = os.path.join(dir, name)
4187
4188             if is_suppressed(path):
4189                 LOG.info('suppressed activate request on %s', path)
4190                 continue
4191
4192             LOG.info('Activating %s', path)
4193             with activate_lock:
4194                 try:
4195                     # never map dmcrypt cyphertext devices
4196                     (cluster, osd_id) = mount_activate(
4197                         dev=path,
4198                         activate_key_template=args.activate_key_template,
4199                         init=args.mark_init,
4200                         dmcrypt=False,
4201                         dmcrypt_key_dir='',
4202                     )
4203                     start_daemon(
4204                         cluster=cluster,
4205                         osd_id=osd_id,
4206                     )
4207
4208                 except Exception as e:
4209                     print(
4210                         '{prog}: {msg}'.format(prog=args.prog, msg=e),
4211                         file=sys.stderr
4212                     )
4213
4214                     err = True
4215
4216     if err:
4217         raise Error('One or more partitions failed to activate')
4218
4219
4220 ###########################
4221
4222 def is_swap(dev):
4223     dev = os.path.realpath(dev)
4224     with open(PROCDIR + '/swaps', 'rb') as proc_swaps:
4225         for line in proc_swaps.readlines()[1:]:
4226             fields = line.split()
4227             if len(fields) < 3:
4228                 continue
4229             swaps_dev = fields[0]
4230             if os.path.isabs(swaps_dev) and os.path.exists(swaps_dev):
4231                 swaps_dev = os.path.realpath(swaps_dev)
4232                 if swaps_dev == dev:
4233                     return True
4234     return False
4235
4236
4237 def get_oneliner(base, name):
4238     path = os.path.join(base, name)
4239     if os.path.isfile(path):
4240         with open(path, 'rb') as _file:
4241             return _bytes2str(_file.readline().rstrip())
4242     return None
4243
4244
4245 def get_dev_fs(dev):
4246     if FREEBSD:
4247         fstype, _, ret = command(
4248             [
4249                 'fstyp',
4250                 '-u',
4251                 dev,
4252             ],
4253         )
4254         if ret == 0:
4255             return fstype
4256     else:
4257         fscheck, _, _ = command(
4258             [
4259                 'blkid',
4260                 '-s',
4261                 'TYPE',
4262                 dev,
4263             ],
4264         )
4265         if 'TYPE' in fscheck:
4266             fstype = fscheck.split()[1].split('"')[1]
4267             return fstype
4268     return None
4269
4270
4271 def split_dev_base_partnum(dev):
4272     if is_mpath(dev):
4273         partnum = partnum_mpath(dev)
4274         base = get_partition_base_mpath(dev)
4275     else:
4276         b = block_path(dev)
4277         partnum = open(os.path.join(b, 'partition')).read().strip()
4278         base = get_partition_base(dev)
4279     return base, partnum
4280
4281
4282 def get_partition_type(part):
4283     return get_blkid_partition_info(part, 'ID_PART_ENTRY_TYPE')
4284
4285
4286 def get_partition_uuid(part):
4287     return get_blkid_partition_info(part, 'ID_PART_ENTRY_UUID')
4288
4289
4290 def get_blkid_partition_info(dev, what=None):
4291     out, _, _ = command(
4292         [
4293             'blkid',
4294             '-o',
4295             'udev',
4296             '-p',
4297             dev,
4298         ]
4299     )
4300     p = {}
4301     for line in out.splitlines():
4302         (key, value) = line.split('=')
4303         p[key] = value
4304     if what:
4305         return p.get(what)
4306     else:
4307         return p
4308
4309
4310 def more_osd_info(path, uuid_map, desc):
4311     desc['ceph_fsid'] = get_oneliner(path, 'ceph_fsid')
4312     if desc['ceph_fsid']:
4313         desc['cluster'] = find_cluster_by_uuid(desc['ceph_fsid'])
4314     desc['whoami'] = get_oneliner(path, 'whoami')
4315     for name in Space.NAMES:
4316         uuid = get_oneliner(path, name + '_uuid')
4317         if uuid:
4318             desc[name + '_uuid'] = uuid.lower()
4319             if desc[name + '_uuid'] in uuid_map:
4320                 desc[name + '_dev'] = uuid_map[desc[name + '_uuid']]
4321
4322
4323 def list_dev_osd(dev, uuid_map, desc):
4324     desc['mount'] = is_mounted(dev)
4325     desc['fs_type'] = get_dev_fs(dev)
4326     desc['state'] = 'unprepared'
4327     if desc['mount']:
4328         desc['state'] = 'active'
4329         more_osd_info(desc['mount'], uuid_map, desc)
4330     elif desc['fs_type']:
4331         try:
4332             tpath = mount(dev=dev, fstype=desc['fs_type'], options='')
4333             if tpath:
4334                 try:
4335                     magic = get_oneliner(tpath, 'magic')
4336                     if magic is not None:
4337                         desc['magic'] = magic
4338                         desc['state'] = 'prepared'
4339                         more_osd_info(tpath, uuid_map, desc)
4340                 finally:
4341                     unmount(tpath)
4342         except MountError:
4343             pass
4344
4345
4346 def list_dev_lockbox(dev, uuid_map, desc):
4347     desc['mount'] = is_mounted(dev)
4348     desc['fs_type'] = get_dev_fs(dev)
4349     desc['state'] = 'unprepared'
4350     if desc['mount']:
4351         desc['state'] = 'active'
4352         desc['osd_uuid'] = get_oneliner(desc['mount'], 'osd-uuid')
4353     elif desc['fs_type']:
4354         try:
4355             tpath = tempfile.mkdtemp(prefix='mnt.', dir=STATEDIR + '/tmp')
4356             args = ['mount', '-t', 'ext4', dev, tpath]
4357             LOG.debug('Mounting lockbox ' + str(" ".join(args)))
4358             command_check_call(args)
4359             magic = get_oneliner(tpath, 'magic')
4360             if magic is not None:
4361                 desc['magic'] = magic
4362                 desc['state'] = 'prepared'
4363                 desc['osd_uuid'] = get_oneliner(tpath, 'osd-uuid')
4364             unmount(tpath)
4365         except subprocess.CalledProcessError:
4366             pass
4367     if desc.get('osd_uuid') in uuid_map:
4368         desc['lockbox_for'] = uuid_map[desc['osd_uuid']]
4369
4370
4371 def list_format_lockbox_plain(dev):
4372     desc = []
4373     if dev.get('lockbox_for'):
4374         desc.append('for ' + dev['lockbox_for'])
4375     elif dev.get('osd_uuid'):
4376         desc.append('for osd ' + dev['osd_uuid'])
4377     return desc
4378
4379
4380 def list_format_more_osd_info_plain(dev):
4381     desc = []
4382     if dev.get('ceph_fsid'):
4383         if dev.get('cluster'):
4384             desc.append('cluster ' + dev['cluster'])
4385         else:
4386             desc.append('unknown cluster ' + dev['ceph_fsid'])
4387     if dev.get('whoami'):
4388         desc.append('osd.%s' % dev['whoami'])
4389     for name in Space.NAMES:
4390         if dev.get(name + '_dev'):
4391             desc.append(name + ' %s' % dev[name + '_dev'])
4392     return desc
4393
4394
4395 def list_format_dev_plain(dev, prefix=''):
4396     desc = []
4397     if dev['ptype'] == PTYPE['regular']['osd']['ready']:
4398         desc = (['ceph data', dev['state']] +
4399                 list_format_more_osd_info_plain(dev))
4400     elif dev['ptype'] in (PTYPE['regular']['lockbox']['ready'],
4401                           PTYPE['mpath']['lockbox']['ready']):
4402         desc = (['ceph lockbox', dev['state']] +
4403                 list_format_lockbox_plain(dev))
4404     elif Ptype.is_dmcrypt(dev['ptype'], 'osd'):
4405         dmcrypt = dev['dmcrypt']
4406         if not dmcrypt['holders']:
4407             desc = ['ceph data (dmcrypt %s)' % dmcrypt['type'],
4408                     'not currently mapped']
4409         elif len(dmcrypt['holders']) == 1:
4410             holder = get_dev_path(dmcrypt['holders'][0])
4411             desc = ['ceph data (dmcrypt %s %s)' %
4412                     (dmcrypt['type'], holder)]
4413             desc += list_format_more_osd_info_plain(dev)
4414         else:
4415             desc = ['ceph data (dmcrypt %s)' % dmcrypt['type'],
4416                     'holders: ' + ','.join(dmcrypt['holders'])]
4417     elif Ptype.is_regular_space(dev['ptype']):
4418         name = Ptype.space_ptype_to_name(dev['ptype'])
4419         desc.append('ceph ' + name)
4420         if dev.get(name + '_for'):
4421             desc.append('for %s' % dev[name + '_for'])
4422     elif Ptype.is_dmcrypt_space(dev['ptype']):
4423         name = Ptype.space_ptype_to_name(dev['ptype'])
4424         dmcrypt = dev['dmcrypt']
4425         if dmcrypt['holders'] and len(dmcrypt['holders']) == 1:
4426             holder = get_dev_path(dmcrypt['holders'][0])
4427             desc = ['ceph ' + name + ' (dmcrypt %s %s)' %
4428                     (dmcrypt['type'], holder)]
4429         else:
4430             desc = ['ceph ' + name + ' (dmcrypt %s)' % dmcrypt['type']]
4431         if dev.get(name + '_for'):
4432             desc.append('for %s' % dev[name + '_for'])
4433     else:
4434         desc.append(dev['type'])
4435         if dev.get('fs_type'):
4436             desc.append(dev['fs_type'])
4437         elif dev.get('ptype'):
4438             desc.append(dev['ptype'])
4439         if dev.get('mount'):
4440             desc.append('mounted on %s' % dev['mount'])
4441     return '%s%s %s' % (prefix, dev['path'], ', '.join(desc))
4442
4443
4444 def list_format_plain(devices):
4445     lines = []
4446     for device in devices:
4447         if device.get('partitions'):
4448             lines.append('%s :' % device['path'])
4449             for p in sorted(device['partitions'], key=lambda x: x['path']):
4450                 lines.append(list_format_dev_plain(dev=p,
4451                                                    prefix=' '))
4452         else:
4453             lines.append(list_format_dev_plain(dev=device,
4454                                                prefix=''))
4455     return "\n".join(lines)
4456
4457
4458 def list_dev(dev, uuid_map, space_map):
4459     info = {
4460         'path': dev,
4461         'dmcrypt': {},
4462     }
4463
4464     info['is_partition'] = is_partition(dev)
4465     if info['is_partition']:
4466         ptype = get_partition_type(dev)
4467         info['uuid'] = get_partition_uuid(dev)
4468     else:
4469         ptype = 'unknown'
4470     info['ptype'] = ptype
4471     LOG.info("list_dev(dev = " + dev + ", ptype = " + str(ptype) + ")")
4472     if ptype in (PTYPE['regular']['osd']['ready'],
4473                  PTYPE['mpath']['osd']['ready']):
4474         info['type'] = 'data'
4475         if ptype == PTYPE['mpath']['osd']['ready']:
4476             info['multipath'] = True
4477         list_dev_osd(dev, uuid_map, info)
4478     elif ptype in (PTYPE['regular']['lockbox']['ready'],
4479                    PTYPE['mpath']['lockbox']['ready']):
4480         info['type'] = 'lockbox'
4481         if ptype == PTYPE['mpath']['osd']['ready']:
4482             info['multipath'] = True
4483         list_dev_lockbox(dev, uuid_map, info)
4484     elif ptype == PTYPE['plain']['osd']['ready']:
4485         holders = is_held(dev)
4486         info['type'] = 'data'
4487         info['dmcrypt']['holders'] = holders
4488         info['dmcrypt']['type'] = 'plain'
4489         if len(holders) == 1:
4490             list_dev_osd(get_dev_path(holders[0]), uuid_map, info)
4491     elif ptype == PTYPE['luks']['osd']['ready']:
4492         holders = is_held(dev)
4493         info['type'] = 'data'
4494         info['dmcrypt']['holders'] = holders
4495         info['dmcrypt']['type'] = 'LUKS'
4496         if len(holders) == 1:
4497             list_dev_osd(get_dev_path(holders[0]), uuid_map, info)
4498     elif Ptype.is_regular_space(ptype) or Ptype.is_mpath_space(ptype):
4499         name = Ptype.space_ptype_to_name(ptype)
4500         info['type'] = name
4501         if ptype == PTYPE['mpath'][name]['ready']:
4502             info['multipath'] = True
4503         if info.get('uuid') in space_map:
4504             info[name + '_for'] = space_map[info['uuid']]
4505     elif Ptype.is_plain_space(ptype):
4506         name = Ptype.space_ptype_to_name(ptype)
4507         holders = is_held(dev)
4508         info['type'] = name
4509         info['dmcrypt']['type'] = 'plain'
4510         info['dmcrypt']['holders'] = holders
4511         if info.get('uuid') in space_map:
4512             info[name + '_for'] = space_map[info['uuid']]
4513     elif Ptype.is_luks_space(ptype):
4514         name = Ptype.space_ptype_to_name(ptype)
4515         holders = is_held(dev)
4516         info['type'] = name
4517         info['dmcrypt']['type'] = 'LUKS'
4518         info['dmcrypt']['holders'] = holders
4519         if info.get('uuid') in space_map:
4520             info[name + '_for'] = space_map[info['uuid']]
4521     else:
4522         path = is_mounted(dev)
4523         fs_type = get_dev_fs(dev)
4524         if is_swap(dev):
4525             info['type'] = 'swap'
4526         else:
4527             info['type'] = 'other'
4528         if fs_type:
4529             info['fs_type'] = fs_type
4530         if path:
4531             info['mount'] = path
4532
4533     return info
4534
4535
4536 def list_devices():
4537     partmap = list_all_partitions()
4538
4539     uuid_map = {}
4540     space_map = {}
4541     for base, parts in sorted(partmap.items()):
4542         for p in parts:
4543             dev = get_dev_path(p)
4544             part_uuid = get_partition_uuid(dev)
4545             if part_uuid:
4546                 uuid_map[part_uuid] = dev
4547             ptype = get_partition_type(dev)
4548             LOG.debug("main_list: " + dev +
4549                       " ptype = " + str(ptype) +
4550                       " uuid = " + str(part_uuid))
4551             if ptype in Ptype.get_ready_by_name('osd'):
4552                 if Ptype.is_dmcrypt(ptype, 'osd'):
4553                     holders = is_held(dev)
4554                     if len(holders) != 1:
4555                         continue
4556                     dev_to_mount = get_dev_path(holders[0])
4557                 else:
4558                     dev_to_mount = dev
4559
4560                 fs_type = get_dev_fs(dev_to_mount)
4561                 if fs_type is not None:
4562                     mount_options = get_mount_options(cluster='ceph',
4563                                                       fs_type=fs_type)
4564                     try:
4565                         tpath = mount(dev=dev_to_mount,
4566                                       fstype=fs_type, options=mount_options)
4567                         try:
4568                             for name in Space.NAMES:
4569                                 space_uuid = get_oneliner(tpath,
4570                                                           name + '_uuid')
4571                                 if space_uuid:
4572                                     space_map[space_uuid.lower()] = dev
4573                         finally:
4574                             unmount(tpath)
4575                     except MountError:
4576                         pass
4577
4578     LOG.debug("main_list: " + str(partmap) + ", uuid_map = " +
4579               str(uuid_map) + ", space_map = " + str(space_map))
4580
4581     devices = []
4582     for base, parts in sorted(partmap.items()):
4583         if parts:
4584             disk = {'path': get_dev_path(base)}
4585             partitions = []
4586             for p in sorted(parts):
4587                 partitions.append(list_dev(get_dev_path(p),
4588                                            uuid_map,
4589                                            space_map))
4590             disk['partitions'] = partitions
4591             devices.append(disk)
4592         else:
4593             device = list_dev(get_dev_path(base), uuid_map, space_map)
4594             device['path'] = get_dev_path(base)
4595             devices.append(device)
4596     LOG.debug("list_devices: " + str(devices))
4597     return devices
4598
4599
4600 def list_zfs():
4601     try:
4602         out, err, ret = command(
4603             [
4604                 'zfs',
4605                 'list',
4606                 '-o', 'name,mountpoint'
4607             ]
4608         )
4609     except subprocess.CalledProcessError as e:
4610         LOG.info('zfs list -o name,mountpoint '
4611                  'fails.\n (Error: %s)' % e)
4612         raise
4613     lines = out.splitlines()
4614     for line in lines[1:]:
4615         vdevline = line.split()
4616         if os.path.exists(os.path.join(vdevline[1], 'active')):
4617             elems = os.path.split(vdevline[1])
4618             print(vdevline[0], "ceph data, active, cluster ceph,", elems[1],
4619                   "mounted on:", vdevline[1])
4620         else:
4621             print(vdevline[0] + " other, zfs, mounted on: " + vdevline[1])
4622
4623
4624 def main_list(args):
4625     with activate_lock:
4626         if FREEBSD:
4627             main_list_freebsd(args)
4628         else:
4629             main_list_protected(args)
4630
4631
4632 def main_list_protected(args):
4633     devices = list_devices()
4634     if args.path:
4635         paths = []
4636         for path in args.path:
4637             if os.path.exists(path):
4638                 paths.append(os.path.realpath(path))
4639             else:
4640                 paths.append(path)
4641         selected_devices = []
4642         for device in devices:
4643             for path in paths:
4644                 if re.search(path + '$', device['path']):
4645                     selected_devices.append(device)
4646     else:
4647         selected_devices = devices
4648     if args.format == 'json':
4649         print(json.dumps(selected_devices))
4650     else:
4651         output = list_format_plain(selected_devices)
4652         if output:
4653             print(output)
4654
4655
4656 def main_list_freebsd(args):
4657     # Currently accomodate only ZFS Filestore partitions
4658     #   return a list of VDEVs and mountpoints
4659     # > zfs list
4660     # NAME   USED  AVAIL  REFER  MOUNTPOINT
4661     # osd0  1.01G  1.32T  1.01G  /var/lib/ceph/osd/osd.0
4662     # osd1  1.01G  1.32T  1.01G  /var/lib/ceph/osd/osd.1
4663     list_zfs()
4664
4665
4666 ###########################
4667 #
4668 # Mark devices that we want to suppress activates on with a
4669 # file like
4670 #
4671 #  /var/lib/ceph/tmp/suppress-activate.sdb
4672 #
4673 # where the last bit is the sanitized device name (/dev/X without the
4674 # /dev/ prefix) and the is_suppress() check matches a prefix.  That
4675 # means suppressing sdb will stop activate on sdb1, sdb2, etc.
4676 #
4677
4678 def is_suppressed(path):
4679     disk = os.path.realpath(path)
4680     try:
4681         if (not disk.startswith('/dev/') or
4682                 not ldev_is_diskdevice(disk)):
4683             return False
4684         base = get_dev_name(disk)
4685         while len(base):
4686             if os.path.exists(SUPPRESS_PREFIX + base):  # noqa
4687                 return True
4688             base = base[:-1]
4689     except:
4690         return False
4691
4692
4693 def set_suppress(path):
4694     disk = os.path.realpath(path)
4695     if not os.path.exists(disk):
4696         raise Error('does not exist', path)
4697     if not ldev_is_diskdevice(path):
4698         raise Error('not a block device', path)
4699     base = get_dev_name(disk)
4700
4701     with open(SUPPRESS_PREFIX + base, 'w') as f:  # noqa
4702         pass
4703     LOG.info('set suppress flag on %s', base)
4704
4705
4706 def unset_suppress(path):
4707     disk = os.path.realpath(path)
4708     if not os.path.exists(disk):
4709         raise Error('does not exist', path)
4710     if not ldev_is_diskdevice(path):
4711         raise Error('not a block device', path)
4712     assert disk.startswith('/dev/')
4713     base = get_dev_name(disk)
4714
4715     fn = SUPPRESS_PREFIX + base  # noqa
4716     if not os.path.exists(fn):
4717         raise Error('not marked as suppressed', path)
4718
4719     try:
4720         os.unlink(fn)
4721         LOG.info('unset suppress flag on %s', base)
4722     except OSError as e:
4723         raise Error('failed to unsuppress', e)
4724
4725
4726 def main_suppress(args):
4727     set_suppress(args.path)
4728
4729
4730 def main_unsuppress(args):
4731     unset_suppress(args.path)
4732
4733
4734 def main_zap(args):
4735     for dev in args.dev:
4736         zap(dev)
4737
4738
4739 def main_trigger(args):
4740     LOG.debug("main_trigger: " + str(args))
4741     if is_systemd() and not args.sync:
4742         # http://www.freedesktop.org/software/systemd/man/systemd-escape.html
4743         escaped_dev = args.dev[1:].replace('-', '\\x2d')
4744         service = 'ceph-disk@{dev}.service'.format(dev=escaped_dev)
4745         LOG.info('systemd detected, triggering %s' % service)
4746         command(
4747             [
4748                 'systemctl',
4749                 '--no-block',
4750                 'restart',
4751                 service,
4752             ]
4753         )
4754         return
4755     if is_upstart() and not args.sync:
4756         LOG.info('upstart detected, triggering ceph-disk task')
4757         command(
4758             [
4759                 'initctl',
4760                 'emit',
4761                 'ceph-disk',
4762                 'dev={dev}'.format(dev=args.dev),
4763                 'pid={pid}'.format(pid=os.getpid()),
4764             ]
4765         )
4766         return
4767
4768     if get_ceph_user() == 'ceph':
4769         command_check_call(['chown', 'ceph:ceph', args.dev])
4770     parttype = get_partition_type(args.dev)
4771     partid = get_partition_uuid(args.dev)
4772
4773     LOG.info('trigger {dev} parttype {parttype} uuid {partid}'.format(
4774         dev=args.dev,
4775         parttype=parttype,
4776         partid=partid,
4777     ))
4778
4779     ceph_disk = ['ceph-disk']
4780     if args.verbose:
4781         ceph_disk.append('--verbose')
4782
4783     if parttype in (PTYPE['regular']['osd']['ready'],
4784                     PTYPE['mpath']['osd']['ready']):
4785         out, err, ret = command(
4786             ceph_disk +
4787             [
4788                 'activate',
4789                 args.dev,
4790             ]
4791         )
4792
4793     elif parttype in (PTYPE['plain']['osd']['ready'],
4794                       PTYPE['luks']['osd']['ready']):
4795         out, err, ret = command(
4796             ceph_disk +
4797             [
4798                 'activate',
4799                 '--dmcrypt',
4800                 args.dev,
4801             ]
4802         )
4803
4804     elif parttype in (PTYPE['regular']['journal']['ready'],
4805                       PTYPE['mpath']['journal']['ready']):
4806         out, err, ret = command(
4807             ceph_disk +
4808             [
4809                 'activate-journal',
4810                 args.dev,
4811             ]
4812         )
4813
4814     elif parttype in (PTYPE['plain']['journal']['ready'],
4815                       PTYPE['luks']['journal']['ready']):
4816         out, err, ret = command(
4817             ceph_disk +
4818             [
4819                 'activate-journal',
4820                 '--dmcrypt',
4821                 args.dev,
4822             ]
4823         )
4824
4825     elif parttype in (PTYPE['regular']['block']['ready'],
4826                       PTYPE['regular']['block.db']['ready'],
4827                       PTYPE['regular']['block.wal']['ready'],
4828                       PTYPE['mpath']['block']['ready'],
4829                       PTYPE['mpath']['block.db']['ready'],
4830                       PTYPE['mpath']['block.wal']['ready']):
4831         out, err, ret = command(
4832             ceph_disk +
4833             [
4834                 'activate-block',
4835                 args.dev,
4836             ]
4837         )
4838
4839     elif parttype in (PTYPE['plain']['block']['ready'],
4840                       PTYPE['plain']['block.db']['ready'],
4841                       PTYPE['plain']['block.wal']['ready'],
4842                       PTYPE['luks']['block']['ready'],
4843                       PTYPE['luks']['block.db']['ready'],
4844                       PTYPE['luks']['block.wal']['ready']):
4845         out, err, ret = command(
4846             ceph_disk +
4847             [
4848                 'activate-block',
4849                 '--dmcrypt',
4850                 args.dev,
4851             ]
4852         )
4853
4854     elif parttype in (PTYPE['regular']['lockbox']['ready'],
4855                       PTYPE['mpath']['lockbox']['ready']):
4856         out, err, ret = command(
4857             ceph_disk +
4858             [
4859                 'activate-lockbox',
4860                 args.dev,
4861             ]
4862         )
4863
4864     else:
4865         raise Error('unrecognized partition type %s' % parttype)
4866
4867     if ret != 0:
4868         LOG.info(out)
4869         LOG.error(err)
4870         raise Error('return code ' + str(ret))
4871     else:
4872         LOG.debug(out)
4873         LOG.debug(err)
4874
4875
4876 def main_fix(args):
4877     # A hash table containing 'path': ('uid', 'gid', blocking, recursive)
4878     fix_table = [
4879         ('/usr/bin/ceph-mon', 'root', ROOTGROUP, True, False),
4880         ('/usr/bin/ceph-mds', 'root', ROOTGROUP, True, False),
4881         ('/usr/bin/ceph-osd', 'root', ROOTGROUP, True, False),
4882         ('/usr/bin/radosgw', 'root', ROOTGROUP, True, False),
4883         ('/etc/ceph', 'root', ROOTGROUP, True, True),
4884         ('/var/run/ceph', 'ceph', 'ceph', True, True),
4885         ('/var/log/ceph', 'ceph', 'ceph', True, True),
4886         ('/var/log/radosgw', 'ceph', 'ceph', True, True),
4887         ('/var/lib/ceph', 'ceph', 'ceph', True, False),
4888     ]
4889
4890     # Relabel/chown all files under /var/lib/ceph/ recursively (except for osd)
4891     for directory in glob.glob('/var/lib/ceph/*'):
4892         if directory == '/var/lib/ceph/osd':
4893             fix_table.append((directory, 'ceph', 'ceph', True, False))
4894         else:
4895             fix_table.append((directory, 'ceph', 'ceph', True, True))
4896
4897     # Relabel/chown the osds recursively and in parallel
4898     for directory in glob.glob('/var/lib/ceph/osd/*'):
4899         fix_table.append((directory, 'ceph', 'ceph', False, True))
4900
4901     LOG.debug("fix_table: " + str(fix_table))
4902
4903     # The lists of background processes
4904     all_processes = []
4905     permissions_processes = []
4906     selinux_processes = []
4907
4908     # Preliminary checks
4909     if args.selinux or args.all:
4910         out, err, ret = command(['selinuxenabled'])
4911         if ret:
4912             LOG.error('SELinux is not enabled, please enable it, first.')
4913             raise Error('no SELinux')
4914
4915     for daemon in ['ceph-mon', 'ceph-osd', 'ceph-mds', 'radosgw', 'ceph-mgr']:
4916         out, err, ret = command(['pgrep', daemon])
4917         if ret == 0:
4918             LOG.error(daemon + ' is running, please stop it, first')
4919             raise Error(daemon + ' running')
4920
4921     # Relabel the basic system data without the ceph files
4922     if args.system or args.all:
4923         c = ['restorecon', '-R', '/']
4924         for directory, _, _, _, _ in fix_table:
4925             # Skip /var/lib/ceph subdirectories
4926             if directory.startswith('/var/lib/ceph/'):
4927                 continue
4928             c.append('-e')
4929             c.append(directory)
4930
4931         out, err, ret = command(c)
4932
4933         if ret:
4934             LOG.error("Failed to restore labels of the underlying system")
4935             LOG.error(err)
4936             raise Error("basic restore failed")
4937
4938     # Use find to relabel + chown ~simultaenously
4939     if args.all:
4940         for directory, uid, gid, blocking, recursive in fix_table:
4941             # Skip directories/files that are not installed
4942             if not os.access(directory, os.F_OK):
4943                 continue
4944
4945             c = [
4946                 'find',
4947                 directory,
4948                 '-exec',
4949                 'chown',
4950                 ':'.join((uid, gid)),
4951                 '{}',
4952                 '+',
4953                 '-exec',
4954                 'restorecon',
4955                 '{}',
4956                 '+',
4957             ]
4958
4959             # Just pass -maxdepth 0 for non-recursive calls
4960             if not recursive:
4961                 c += ['-maxdepth', '0']
4962
4963             if blocking:
4964                 out, err, ret = command(c)
4965
4966                 if ret:
4967                     LOG.error("Failed to fix " + directory)
4968                     LOG.error(err)
4969                     raise Error(directory + " fix failed")
4970             else:
4971                 all_processes.append(command_init(c))
4972
4973     LOG.debug("all_processes: " + str(all_processes))
4974     for process in all_processes:
4975         out, err, ret = command_wait(process)
4976         if ret:
4977             LOG.error("A background find process failed")
4978             LOG.error(err)
4979             raise Error("background failed")
4980
4981     # Fix permissions
4982     if args.permissions:
4983         for directory, uid, gid, blocking, recursive in fix_table:
4984             # Skip directories/files that are not installed
4985             if not os.access(directory, os.F_OK):
4986                 continue
4987
4988             if recursive:
4989                 c = [
4990                     'chown',
4991                     '-R',
4992                     ':'.join((uid, gid)),
4993                     directory
4994                 ]
4995             else:
4996                 c = [
4997                     'chown',
4998                     ':'.join((uid, gid)),
4999                     directory
5000                 ]
5001
5002             if blocking:
5003                 out, err, ret = command(c)
5004
5005                 if ret:
5006                     LOG.error("Failed to chown " + directory)
5007                     LOG.error(err)
5008                     raise Error(directory + " chown failed")
5009             else:
5010                 permissions_processes.append(command_init(c))
5011
5012     LOG.debug("permissions_processes: " + str(permissions_processes))
5013     for process in permissions_processes:
5014         out, err, ret = command_wait(process)
5015         if ret:
5016             LOG.error("A background permissions process failed")
5017             LOG.error(err)
5018             raise Error("background failed")
5019
5020     # Fix SELinux labels
5021     if args.selinux:
5022         for directory, uid, gid, blocking, recursive in fix_table:
5023             # Skip directories/files that are not installed
5024             if not os.access(directory, os.F_OK):
5025                 continue
5026
5027             if recursive:
5028                 c = [
5029                     'restorecon',
5030                     '-R',
5031                     directory
5032                 ]
5033             else:
5034                 c = [
5035                     'restorecon',
5036                     directory
5037                 ]
5038
5039             if blocking:
5040                 out, err, ret = command(c)
5041
5042                 if ret:
5043                     LOG.error("Failed to restore labels for " + directory)
5044                     LOG.error(err)
5045                     raise Error(directory + " relabel failed")
5046             else:
5047                 selinux_processes.append(command_init(c))
5048
5049     LOG.debug("selinux_processes: " + str(selinux_processes))
5050     for process in selinux_processes:
5051         out, err, ret = command_wait(process)
5052         if ret:
5053             LOG.error("A background selinux process failed")
5054             LOG.error(err)
5055             raise Error("background failed")
5056
5057     LOG.info(
5058         "The ceph files has been fixed, please reboot "
5059         "the system for the changes to take effect."
5060     )
5061
5062
5063 def setup_statedir(dir):
5064     # XXX The following use of globals makes linting
5065     # really hard. Global state in Python is iffy and
5066     # should be avoided.
5067     global STATEDIR
5068     STATEDIR = dir
5069
5070     if not os.path.exists(STATEDIR):
5071         os.mkdir(STATEDIR)
5072     if not os.path.exists(STATEDIR + "/tmp"):
5073         os.mkdir(STATEDIR + "/tmp")
5074
5075     global prepare_lock
5076     prepare_lock = FileLock(STATEDIR + '/tmp/ceph-disk.prepare.lock')
5077
5078     global activate_lock
5079     activate_lock = FileLock(STATEDIR + '/tmp/ceph-disk.activate.lock')
5080
5081     global SUPPRESS_PREFIX
5082     SUPPRESS_PREFIX = STATEDIR + '/tmp/suppress-activate.'
5083
5084
5085 def setup_sysconfdir(dir):
5086     global SYSCONFDIR
5087     SYSCONFDIR = dir
5088
5089
5090 def parse_args(argv):
5091     parser = argparse.ArgumentParser(
5092         'ceph-disk',
5093     )
5094     parser.add_argument(
5095         '-v', '--verbose',
5096         action='store_true', default=None,
5097         help='be more verbose',
5098     )
5099     parser.add_argument(
5100         '--log-stdout',
5101         action='store_true', default=None,
5102         help='log to stdout',
5103     )
5104     parser.add_argument(
5105         '--prepend-to-path',
5106         metavar='PATH',
5107         default='/usr/bin',
5108         help=('prepend PATH to $PATH for backward compatibility '
5109               '(default /usr/bin)'),
5110     )
5111     parser.add_argument(
5112         '--statedir',
5113         metavar='PATH',
5114         default='/var/lib/ceph',
5115         help=('directory in which ceph state is preserved '
5116               '(default /var/lib/ceph)'),
5117     )
5118     parser.add_argument(
5119         '--sysconfdir',
5120         metavar='PATH',
5121         default='/etc/ceph',
5122         help=('directory in which ceph configuration files are found '
5123               '(default /etc/ceph)'),
5124     )
5125     parser.add_argument(
5126         '--setuser',
5127         metavar='USER',
5128         default=None,
5129         help='use the given user for subprocesses, rather than ceph or root'
5130     )
5131     parser.add_argument(
5132         '--setgroup',
5133         metavar='GROUP',
5134         default=None,
5135         help='use the given group for subprocesses, rather than ceph or root'
5136     )
5137     parser.set_defaults(
5138         # we want to hold on to this, for later
5139         prog=parser.prog,
5140     )
5141
5142     subparsers = parser.add_subparsers(
5143         title='subcommands',
5144         description='valid subcommands',
5145         help='sub-command help',
5146     )
5147
5148     Prepare.set_subparser(subparsers)
5149     make_activate_parser(subparsers)
5150     make_activate_lockbox_parser(subparsers)
5151     make_activate_block_parser(subparsers)
5152     make_activate_journal_parser(subparsers)
5153     make_activate_all_parser(subparsers)
5154     make_list_parser(subparsers)
5155     make_suppress_parser(subparsers)
5156     make_deactivate_parser(subparsers)
5157     make_destroy_parser(subparsers)
5158     make_zap_parser(subparsers)
5159     make_trigger_parser(subparsers)
5160     make_fix_parser(subparsers)
5161
5162     args = parser.parse_args(argv)
5163     return args
5164
5165
5166 def make_fix_parser(subparsers):
5167     fix_parser = subparsers.add_parser(
5168         'fix',
5169         formatter_class=argparse.RawDescriptionHelpFormatter,
5170         description=textwrap.fill(textwrap.dedent("""\
5171         """)),
5172         help='fix SELinux labels and/or file permissions')
5173
5174     fix_parser.add_argument(
5175         '--system',
5176         action='store_true',
5177         default=False,
5178         help='fix SELinux labels for the non-ceph system data'
5179     )
5180     fix_parser.add_argument(
5181         '--selinux',
5182         action='store_true',
5183         default=False,
5184         help='fix SELinux labels for ceph data'
5185     )
5186     fix_parser.add_argument(
5187         '--permissions',
5188         action='store_true',
5189         default=False,
5190         help='fix file permissions for ceph data'
5191     )
5192     fix_parser.add_argument(
5193         '--all',
5194         action='store_true',
5195         default=False,
5196         help='perform all the fix-related operations'
5197     )
5198     fix_parser.set_defaults(
5199         func=main_fix,
5200     )
5201     return fix_parser
5202
5203
5204 def make_trigger_parser(subparsers):
5205     trigger_parser = subparsers.add_parser(
5206         'trigger',
5207         formatter_class=argparse.RawDescriptionHelpFormatter,
5208         description=textwrap.fill(textwrap.dedent("""\
5209         The partition given in argument is activated. The type of the
5210         partition (data, lockbox, journal etc.) is detected by its
5211         type. If the init system is upstart or systemd, the activation is
5212         delegated to it and runs asynchronously, which
5213         helps reduce the execution time of udev actions.
5214         """)),
5215         help='activate any device (called by udev)')
5216     trigger_parser.add_argument(
5217         'dev',
5218         help=('device'),
5219     )
5220     trigger_parser.add_argument(
5221         '--cluster',
5222         metavar='NAME',
5223         default='ceph',
5224         help='cluster name to assign this disk to',
5225     )
5226     trigger_parser.add_argument(
5227         '--dmcrypt',
5228         action='store_true', default=None,
5229         help='map devices with dm-crypt',
5230     )
5231     trigger_parser.add_argument(
5232         '--dmcrypt-key-dir',
5233         metavar='KEYDIR',
5234         default='/etc/ceph/dmcrypt-keys',
5235         help='directory where dm-crypt keys are stored',
5236     )
5237     trigger_parser.add_argument(
5238         '--sync',
5239         action='store_true', default=None,
5240         help='do operation synchronously; do not trigger systemd',
5241     )
5242     trigger_parser.set_defaults(
5243         func=main_trigger,
5244     )
5245     return trigger_parser
5246
5247
5248 def make_activate_parser(subparsers):
5249     activate_parser = subparsers.add_parser(
5250         'activate',
5251         formatter_class=argparse.RawDescriptionHelpFormatter,
5252         description=textwrap.fill(textwrap.dedent("""\
5253         Activate the OSD found at PATH (can be a directory
5254         or a device partition, possibly encrypted). When
5255         activated for the first time, a unique OSD id is obtained
5256         from the cluster. If PATH is a directory, a symbolic
5257         link is added in {statedir}/osd/ceph-$id. If PATH is
5258         a partition, it is mounted on {statedir}/osd/ceph-$id.
5259         Finally, the OSD daemon is run.
5260
5261         If the OSD depends on auxiliary partitions (journal, block, ...)
5262         they need to be available otherwise activation will fail. It
5263         may happen if a journal is encrypted and cryptsetup was not
5264         run yet.
5265         """.format(statedir=STATEDIR))),
5266         help='Activate a Ceph OSD')
5267     activate_parser.add_argument(
5268         '--mount',
5269         action='store_true', default=None,
5270         help='mount a block device [deprecated, ignored]',
5271     )
5272     activate_parser.add_argument(
5273         '--activate-key',
5274         metavar='PATH',
5275         help='bootstrap-osd keyring path template (%(default)s)',
5276         dest='activate_key_template',
5277     )
5278     activate_parser.add_argument(
5279         '--mark-init',
5280         metavar='INITSYSTEM',
5281         help='init system to manage this dir',
5282         default='auto',
5283         choices=INIT_SYSTEMS,
5284     )
5285     activate_parser.add_argument(
5286         '--no-start-daemon',
5287         action='store_true', default=None,
5288         help='do not start the daemon',
5289     )
5290     activate_parser.add_argument(
5291         'path',
5292         metavar='PATH',
5293         help='path to block device or directory',
5294     )
5295     activate_parser.add_argument(
5296         '--dmcrypt',
5297         action='store_true', default=None,
5298         help='map DATA and/or JOURNAL devices with dm-crypt',
5299     )
5300     activate_parser.add_argument(
5301         '--dmcrypt-key-dir',
5302         metavar='KEYDIR',
5303         default='/etc/ceph/dmcrypt-keys',
5304         help='directory where dm-crypt keys are stored',
5305     )
5306     activate_parser.add_argument(
5307         '--reactivate',
5308         action='store_true', default=False,
5309         help='activate the deactived OSD',
5310     )
5311     activate_parser.set_defaults(
5312         activate_key_template='{statedir}/bootstrap-osd/{cluster}.keyring',
5313         func=main_activate,
5314     )
5315     return activate_parser
5316
5317
5318 def make_activate_lockbox_parser(subparsers):
5319     parser = subparsers.add_parser(
5320         'activate-lockbox',
5321         formatter_class=argparse.RawDescriptionHelpFormatter,
5322         description=textwrap.fill(textwrap.dedent("""\
5323         Mount the partition found at PATH on {statedir}/osd-lockbox/$uuid
5324         where $uuid uniquely identifies the OSD that needs this lockbox
5325         to retrieve keys from the monitor and unlock its partitions.
5326
5327         If the OSD has one or more auxiliary devices (journal, block, ...)
5328         symbolic links are created at {statedir}/osd-lockbox/$other_uuid
5329         and point to {statedir}/osd-lockbox/$uuid. This will, for instance,
5330         allow a journal encrypted in a partition identified by $other_uuid to
5331         fetch the keys it needs from the monitor.
5332
5333         Finally the OSD is activated, as it would be with ceph-disk activate.
5334         """.format(statedir=STATEDIR))),
5335         help='Activate a Ceph lockbox')
5336     parser.add_argument(
5337         '--activate-key',
5338         help='bootstrap-osd keyring path template (%(default)s)',
5339         dest='activate_key_template',
5340     )
5341     parser.add_argument(
5342         '--dmcrypt-key-dir',
5343         metavar='KEYDIR',
5344         default='/etc/ceph/dmcrypt-keys',
5345         help='directory where dm-crypt keys are stored',
5346     )
5347     parser.add_argument(
5348         'path',
5349         metavar='PATH',
5350         help='path to block device',
5351     )
5352     parser.set_defaults(
5353         activate_key_template='{statedir}/bootstrap-osd/{cluster}.keyring',
5354         func=main_activate_lockbox,
5355     )
5356     return parser
5357
5358
5359 def make_activate_block_parser(subparsers):
5360     return make_activate_space_parser('block', subparsers)
5361
5362
5363 def make_activate_journal_parser(subparsers):
5364     return make_activate_space_parser('journal', subparsers)
5365
5366
5367 def make_activate_space_parser(name, subparsers):
5368     activate_space_parser = subparsers.add_parser(
5369         'activate-%s' % name,
5370         formatter_class=argparse.RawDescriptionHelpFormatter,
5371         description=textwrap.fill(textwrap.dedent("""\
5372         Activating a {name} partition is only meaningfull
5373         if it is encrypted and it will map it using
5374         cryptsetup.
5375
5376         Finally the corresponding OSD is activated,
5377         as it would be with ceph-disk activate.
5378         """.format(name=name))),
5379         help='Activate an OSD via its %s device' % name)
5380     activate_space_parser.add_argument(
5381         'dev',
5382         metavar='DEV',
5383         help='path to %s block device' % name,
5384     )
5385     activate_space_parser.add_argument(
5386         '--activate-key',
5387         metavar='PATH',
5388         help='bootstrap-osd keyring path template (%(default)s)',
5389         dest='activate_key_template',
5390     )
5391     activate_space_parser.add_argument(
5392         '--mark-init',
5393         metavar='INITSYSTEM',
5394         help='init system to manage this dir',
5395         default='auto',
5396         choices=INIT_SYSTEMS,
5397     )
5398     activate_space_parser.add_argument(
5399         '--dmcrypt',
5400         action='store_true', default=None,
5401         help=('map data and/or auxiliariy (journal, etc.) '
5402               'devices with dm-crypt'),
5403     )
5404     activate_space_parser.add_argument(
5405         '--dmcrypt-key-dir',
5406         metavar='KEYDIR',
5407         default='/etc/ceph/dmcrypt-keys',
5408         help='directory where dm-crypt keys are stored',
5409     )
5410     activate_space_parser.add_argument(
5411         '--reactivate',
5412         action='store_true', default=False,
5413         help='activate the deactived OSD',
5414     )
5415     activate_space_parser.set_defaults(
5416         activate_key_template='{statedir}/bootstrap-osd/{cluster}.keyring',
5417         func=lambda args: main_activate_space(name, args),
5418     )
5419     return activate_space_parser
5420
5421
5422 def make_activate_all_parser(subparsers):
5423     activate_all_parser = subparsers.add_parser(
5424         'activate-all',
5425         formatter_class=argparse.RawDescriptionHelpFormatter,
5426         description=textwrap.fill(textwrap.dedent("""\
5427         Activate all OSD partitions found in /dev/disk/by-parttypeuuid.
5428         The partitions containing auxiliary devices (journal, block, ...)
5429         are not activated.
5430         """)),
5431         help='Activate all tagged OSD partitions')
5432     activate_all_parser.add_argument(
5433         '--activate-key',
5434         metavar='PATH',
5435         help='bootstrap-osd keyring path template (%(default)s)',
5436         dest='activate_key_template',
5437     )
5438     activate_all_parser.add_argument(
5439         '--mark-init',
5440         metavar='INITSYSTEM',
5441         help='init system to manage this dir',
5442         default='auto',
5443         choices=INIT_SYSTEMS,
5444     )
5445     activate_all_parser.set_defaults(
5446         activate_key_template='{statedir}/bootstrap-osd/{cluster}.keyring',
5447         func=main_activate_all,
5448     )
5449     return activate_all_parser
5450
5451
5452 def make_list_parser(subparsers):
5453     list_parser = subparsers.add_parser(
5454         'list',
5455         formatter_class=argparse.RawDescriptionHelpFormatter,
5456         description=textwrap.fill(textwrap.dedent("""\
5457         Display all partitions on the system and their
5458         associated Ceph information, if any.
5459         """)),
5460         help='List disks, partitions, and Ceph OSDs')
5461     list_parser.add_argument(
5462         '--format',
5463         help='output format',
5464         default='plain',
5465         choices=['json', 'plain'],
5466     )
5467     list_parser.add_argument(
5468         'path',
5469         metavar='PATH',
5470         nargs='*',
5471         help='path to block devices, relative to /sys/block',
5472     )
5473     list_parser.set_defaults(
5474         func=main_list,
5475     )
5476     return list_parser
5477
5478
5479 def make_suppress_parser(subparsers):
5480     suppress_parser = subparsers.add_parser(
5481         'suppress-activate',
5482         formatter_class=argparse.RawDescriptionHelpFormatter,
5483         description=textwrap.fill(textwrap.dedent("""\
5484         Add a prefix to the list of suppressed device names
5485         so that they are ignored by all activate* subcommands.
5486         """)),
5487         help='Suppress activate on a device (prefix)')
5488     suppress_parser.add_argument(
5489         'path',
5490         metavar='PATH',
5491         help='path to block device or directory',
5492     )
5493     suppress_parser.set_defaults(
5494         func=main_suppress,
5495     )
5496
5497     unsuppress_parser = subparsers.add_parser(
5498         'unsuppress-activate',
5499         formatter_class=argparse.RawDescriptionHelpFormatter,
5500         description=textwrap.fill(textwrap.dedent("""\
5501         Remove a prefix from the list of suppressed device names
5502         so that they are no longer ignored by all
5503         activate* subcommands.
5504         """)),
5505         help='Stop suppressing activate on a device (prefix)')
5506     unsuppress_parser.add_argument(
5507         'path',
5508         metavar='PATH',
5509         help='path to block device or directory',
5510     )
5511     unsuppress_parser.set_defaults(
5512         func=main_unsuppress,
5513     )
5514     return suppress_parser
5515
5516
5517 def make_deactivate_parser(subparsers):
5518     deactivate_parser = subparsers.add_parser(
5519         'deactivate',
5520         formatter_class=argparse.RawDescriptionHelpFormatter,
5521         description=textwrap.fill(textwrap.dedent("""\
5522         Deactivate the OSD located at PATH. It stops the OSD daemon
5523         and optionally marks it out (with --mark-out). The content of
5524         the OSD is left untouched.
5525
5526         By default, the, ready, active, INIT-specific files are
5527         removed (so that it is not automatically re-activated by the
5528         udev rules or ceph-disk trigger) and the file deactive is
5529         created to remember the OSD is deactivated.
5530
5531         If the --once option is given, the ready, active, INIT-specific
5532         files are not removed and the OSD will reactivate whenever
5533         ceph-disk trigger is run on one of the devices (journal, data,
5534         block, lockbox, ...).
5535
5536         If the OSD is dmcrypt, remove the data dmcrypt map. When
5537         deactivate finishes, the OSD is down.
5538         """)),
5539         help='Deactivate a Ceph OSD')
5540     deactivate_parser.add_argument(
5541         '--cluster',
5542         metavar='NAME',
5543         default='ceph',
5544         help='cluster name to assign this disk to',
5545     )
5546     deactivate_parser.add_argument(
5547         'path',
5548         metavar='PATH',
5549         nargs='?',
5550         help='path to block device or directory',
5551     )
5552     deactivate_parser.add_argument(
5553         '--deactivate-by-id',
5554         metavar='<id>',
5555         help='ID of OSD to deactive'
5556     )
5557     deactivate_parser.add_argument(
5558         '--mark-out',
5559         action='store_true', default=False,
5560         help='option to mark the osd out',
5561     )
5562     deactivate_parser.add_argument(
5563         '--once',
5564         action='store_true', default=False,
5565         help='does not need --reactivate to activate again',
5566     )
5567     deactivate_parser.set_defaults(
5568         func=main_deactivate,
5569     )
5570
5571
5572 def make_destroy_parser(subparsers):
5573     destroy_parser = subparsers.add_parser(
5574         'destroy',
5575         formatter_class=argparse.RawDescriptionHelpFormatter,
5576         description=textwrap.fill(textwrap.dedent("""\ Destroy the OSD located at PATH.  It removes the OSD from the
5577         cluster and marks it destroyed. An OSD must be down before it
5578         can be destroyed. Once it is destroyed, a new OSD can be created
5579         in its place, reusing the same OSD id and position (e.g. after
5580         a failed HDD or SSD is replaced).  Alternatively, if the
5581         --purge option is also specified, the OSD is removed from the
5582         CRUSH map and the OSD id is deallocated.""")),
5583         help='Destroy a Ceph OSD')
5584     destroy_parser.add_argument(
5585         '--cluster',
5586         metavar='NAME',
5587         default='ceph',
5588         help='cluster name to assign this disk to',
5589     )
5590     destroy_parser.add_argument(
5591         'path',
5592         metavar='PATH',
5593         nargs='?',
5594         help='path to block device or directory',
5595     )
5596     destroy_parser.add_argument(
5597         '--destroy-by-id',
5598         metavar='<id>',
5599         help='ID of OSD to destroy'
5600     )
5601     destroy_parser.add_argument(
5602         '--dmcrypt-key-dir',
5603         metavar='KEYDIR',
5604         default='/etc/ceph/dmcrypt-keys',
5605         help=('directory where dm-crypt keys are stored '
5606               '(If you don\'t know how it work, '
5607               'dont use it. we have default value)'),
5608     )
5609     destroy_parser.add_argument(
5610         '--zap',
5611         action='store_true', default=False,
5612         help='option to erase data and partition',
5613     )
5614     destroy_parser.add_argument(
5615         '--purge',
5616         action='store_true', default=False,
5617         help='option to remove OSD from CRUSH map and deallocate the id',
5618     )
5619     destroy_parser.set_defaults(
5620         func=main_destroy,
5621     )
5622
5623
5624 def make_zap_parser(subparsers):
5625     zap_parser = subparsers.add_parser(
5626         'zap',
5627         formatter_class=argparse.RawDescriptionHelpFormatter,
5628         description=textwrap.fill(textwrap.dedent("""\
5629         Zap/erase/destroy a device's partition table and contents. It
5630         actually uses sgdisk and it's option --zap-all to
5631         destroy both GPT and MBR data structures so that the disk
5632         becomes suitable for repartitioning.
5633         """)),
5634         help='Zap/erase/destroy a device\'s partition table (and contents)')
5635     zap_parser.add_argument(
5636         'dev',
5637         metavar='DEV',
5638         nargs='+',
5639         help='path to block device',
5640     )
5641     zap_parser.set_defaults(
5642         func=main_zap,
5643     )
5644     return zap_parser
5645
5646
5647 def main(argv):
5648     args = parse_args(argv)
5649
5650     setup_logging(args.verbose, args.log_stdout)
5651
5652     if args.prepend_to_path != '':
5653         path = os.environ.get('PATH', os.defpath)
5654         os.environ['PATH'] = args.prepend_to_path + ":" + path
5655
5656     if args.func.__name__ != 'main_trigger':
5657         # trigger may run when statedir is unavailable and does not use it
5658         setup_statedir(args.statedir)
5659     setup_sysconfdir(args.sysconfdir)
5660
5661     global CEPH_PREF_USER
5662     CEPH_PREF_USER = args.setuser
5663     global CEPH_PREF_GROUP
5664     CEPH_PREF_GROUP = args.setgroup
5665
5666     if args.verbose:
5667         args.func(args)
5668     else:
5669         main_catch(args.func, args)
5670
5671
5672 def setup_logging(verbose, log_stdout):
5673     loglevel = logging.WARNING
5674     if verbose:
5675         loglevel = logging.DEBUG
5676
5677     if log_stdout:
5678         ch = logging.StreamHandler(stream=sys.stdout)
5679         ch.setLevel(loglevel)
5680         formatter = logging.Formatter('%(funcName)s: %(message)s')
5681         ch.setFormatter(formatter)
5682         LOG.addHandler(ch)
5683         LOG.setLevel(loglevel)
5684     else:
5685         logging.basicConfig(
5686             level=loglevel,
5687             format='%(funcName)s: %(message)s',
5688         )
5689
5690
5691 def main_catch(func, args):
5692
5693     try:
5694         func(args)
5695
5696     except Error as e:
5697         raise SystemExit(
5698             '{prog}: {msg}'.format(
5699                 prog=args.prog,
5700                 msg=e,
5701             )
5702         )
5703
5704     except CephDiskException as error:
5705         exc_name = error.__class__.__name__
5706         raise SystemExit(
5707             '{prog} {exc_name}: {msg}'.format(
5708                 prog=args.prog,
5709                 exc_name=exc_name,
5710                 msg=error,
5711             )
5712         )
5713
5714
5715 def run():
5716     main(sys.argv[1:])
5717
5718
5719 if __name__ == '__main__':
5720     main(sys.argv[1:])
5721     warned_about = {}