1 from __future__
import print_function
8 from textwrap
import dedent
9 from ceph_volume
import process
, decorators
, terminal
, conf
10 from ceph_volume
.util
import system
, disk
11 from ceph_volume
.util
import encryption
as encryption_utils
12 from ceph_volume
.systemd
import systemctl
15 logger
= logging
.getLogger(__name__
)
16 mlogger
= terminal
.MultiLogger(__name__
)
19 class Activate(object):
21 help = 'Enable systemd units to mount configured devices and start a Ceph OSD'
23 def __init__(self
, argv
, from_trigger
=False):
25 self
.from_trigger
= from_trigger
26 self
.skip_systemd
= False
28 def validate_devices(self
, json_config
):
30 ``json_config`` is the loaded dictionary coming from the JSON file. It is usually mixed with
31 other non-device items, but for sakes of comparison it doesn't really matter. This method is
32 just making sure that the keys needed exist
34 devices
= json_config
.keys()
36 objectstore
= json_config
['type']
38 logger
.warning('"type" was not defined, will assume "bluestore"')
39 objectstore
= 'bluestore'
41 # Go through all the device combinations that are absolutely required,
42 # raise an error describing what was expected and what was found
44 if objectstore
== 'filestore':
45 if {'data', 'journal'}.issubset(set(devices
)):
48 found
= [i
for i
in devices
if i
in ['data', 'journal']]
49 mlogger
.error("Required devices (data, and journal) not present for filestore")
50 mlogger
.error('filestore devices found: %s', found
)
51 raise RuntimeError('Unable to activate filestore OSD due to missing devices')
53 # This is a bit tricky, with newer bluestore we don't need data, older implementations
54 # do (e.g. with ceph-disk). ceph-volume just uses a tmpfs that doesn't require data.
55 if {'block', 'data'}.issubset(set(devices
)):
58 bluestore_devices
= ['block.db', 'block.wal', 'block', 'data']
59 found
= [i
for i
in devices
if i
in bluestore_devices
]
60 mlogger
.error("Required devices (block and data) not present for bluestore")
61 mlogger
.error('bluestore devices found: %s', found
)
62 raise RuntimeError('Unable to activate bluestore OSD due to missing devices')
64 def get_device(self
, uuid
):
66 If a device is encrypted, it will decrypt/open and return the mapper
67 path, if it isn't encrypted it will just return the device found that
68 is mapped to the uuid. This will make it easier for the caller to
69 avoid if/else to check if devices need decrypting
71 :param uuid: The partition uuid of the device (PARTUUID)
73 device
= disk
.get_device_from_partuuid(uuid
)
75 # If device is not found, it is fine to return an empty string from the
76 # helper that finds `device`. If it finds anything and it is not
77 # encrypted, just return what was found
78 if not self
.is_encrypted
or not device
:
81 if self
.encryption_type
== 'luks':
82 encryption_utils
.luks_open(self
.dmcrypt_secret
, device
, uuid
)
84 encryption_utils
.plain_open(self
.dmcrypt_secret
, device
, uuid
)
86 return '/dev/mapper/%s' % uuid
88 def enable_systemd_units(self
, osd_id
, osd_fsid
):
90 * disables the ceph-disk systemd units to prevent them from running when
91 a UDEV event matches Ceph rules
92 * creates the ``simple`` systemd units to handle the activation and
93 startup of the OSD with ``osd_id`` and ``osd_fsid``
94 * enables the OSD systemd unit and finally starts the OSD.
96 if not self
.from_trigger
and not self
.skip_systemd
:
97 # means it was scanned and now activated directly, so ensure that
98 # ceph-disk units are disabled, and that the `simple` systemd unit
99 # is created and enabled
101 # enable the ceph-volume unit for this OSD
102 systemctl
.enable_volume(osd_id
, osd_fsid
, 'simple')
104 # disable any/all ceph-disk units
105 systemctl
.mask_ceph_disk()
107 ('All ceph-disk systemd units have been disabled to '
108 'prevent OSDs getting triggered by UDEV events')
111 terminal
.info('Skipping enabling of `simple` systemd unit')
112 terminal
.info('Skipping masking of ceph-disk systemd units')
114 if not self
.skip_systemd
:
116 systemctl
.enable_osd(osd_id
)
119 systemctl
.start_osd(osd_id
)
122 'Skipping enabling and starting OSD simple systemd unit because --no-systemd was used'
125 @decorators.needs_root
126 def activate(self
, args
):
127 with
open(args
.json_config
, 'r') as fp
:
128 osd_metadata
= json
.load(fp
)
130 # Make sure that required devices are configured
131 self
.validate_devices(osd_metadata
)
133 osd_id
= osd_metadata
.get('whoami', args
.osd_id
)
134 osd_fsid
= osd_metadata
.get('fsid', args
.osd_fsid
)
135 data_uuid
= osd_metadata
.get('data', {}).get('uuid')
136 conf
.cluster
= osd_metadata
.get('cluster_name', 'ceph')
139 'Unable to activate OSD %s - no "uuid" key found for data' % args
.osd_id
142 # Encryption detection, and capturing of the keys to decrypt
143 self
.is_encrypted
= osd_metadata
.get('encrypted', False)
144 self
.encryption_type
= osd_metadata
.get('encryption_type')
145 if self
.is_encrypted
:
146 lockbox_secret
= osd_metadata
.get('lockbox.keyring')
147 # write the keyring always so that we can unlock
148 encryption_utils
.write_lockbox_keyring(osd_id
, osd_fsid
, lockbox_secret
)
149 # Store the secret around so that the decrypt method can reuse
150 raw_dmcrypt_secret
= encryption_utils
.get_dmcrypt_key(osd_id
, osd_fsid
)
151 # Note how both these calls need b64decode. For some reason, the
152 # way ceph-disk creates these keys, it stores them in the monitor
153 # *undecoded*, requiring this decode call again. The lvm side of
154 # encryption doesn't need it, so we are assuming here that anything
155 # that `simple` scans, will come from ceph-disk and will need this
156 # extra decode call here
157 self
.dmcrypt_secret
= base64
.b64decode(raw_dmcrypt_secret
)
159 cluster_name
= osd_metadata
.get('cluster_name', 'ceph')
160 osd_dir
= '/var/lib/ceph/osd/%s-%s' % (cluster_name
, osd_id
)
162 # XXX there is no support for LVM here
163 data_device
= self
.get_device(data_uuid
)
164 journal_device
= self
.get_device(osd_metadata
.get('journal', {}).get('uuid'))
165 block_device
= self
.get_device(osd_metadata
.get('block', {}).get('uuid'))
166 block_db_device
= self
.get_device(osd_metadata
.get('block.db', {}).get('uuid'))
167 block_wal_device
= self
.get_device(osd_metadata
.get('block.wal', {}).get('uuid'))
169 if not system
.device_is_mounted(data_device
, destination
=osd_dir
):
170 process
.run(['mount', '-v', data_device
, osd_dir
])
173 'journal': journal_device
,
174 'block': block_device
,
175 'block.db': block_db_device
,
176 'block.wal': block_wal_device
179 for name
, device
in device_map
.items():
182 # always re-do the symlink regardless if it exists, so that the journal
183 # device path that may have changed can be mapped correctly every time
184 destination
= os
.path
.join(osd_dir
, name
)
185 process
.run(['ln', '-snf', device
, destination
])
187 # make sure that the journal has proper permissions
190 self
.enable_systemd_units(osd_id
, osd_fsid
)
192 terminal
.success('Successfully activated OSD %s with FSID %s' % (osd_id
, osd_fsid
))
195 sub_command_help
= dedent("""
196 Activate OSDs by mounting devices previously configured to their
197 appropriate destination::
199 ceph-volume simple activate {ID} {FSID}
201 Or using a JSON file directly::
203 ceph-volume simple activate --file /etc/ceph/osd/{ID}-{FSID}.json
205 The OSD must have been "scanned" previously (see ``ceph-volume simple
206 scan``), so that all needed OSD device information and metadata exist.
208 A previously scanned OSD would exist like::
210 /etc/ceph/osd/{ID}-{FSID}.json
213 Environment variables supported:
215 CEPH_VOLUME_SIMPLE_JSON_DIR: Directory location for scanned OSD JSON configs
217 parser
= argparse
.ArgumentParser(
218 prog
='ceph-volume simple activate',
219 formatter_class
=argparse
.RawDescriptionHelpFormatter
,
220 description
=sub_command_help
,
226 help='The ID of the OSD, usually an integer, like 0'
232 help='The FSID of the OSD, similar to a SHA1'
236 help='Activate all OSDs with a OSD JSON config',
242 help='The path to a JSON file, from a scanned OSD'
248 help='Skip creating and enabling systemd units and starting OSD services',
250 if len(self
.argv
) == 0:
251 print(sub_command_help
)
253 args
= parser
.parse_args(self
.argv
)
254 if not args
.file and not args
.all
:
255 if not args
.osd_id
and not args
.osd_fsid
:
256 terminal
.error('ID and FSID are required to find the right OSD to activate')
257 terminal
.error('from a scanned OSD location in /etc/ceph/osd/')
258 raise RuntimeError('Unable to activate without both ID and FSID')
259 # don't allow a CLI flag to specify the JSON dir, because that might
260 # implicitly indicate that it would be possible to activate a json file
261 # at a non-default location which would not work at boot time if the
262 # custom location is not exposed through an ENV var
263 self
.skip_systemd
= args
.skip_systemd
264 json_dir
= os
.environ
.get('CEPH_VOLUME_SIMPLE_JSON_DIR', '/etc/ceph/osd/')
266 if args
.file or args
.osd_id
:
267 mlogger
.warn('--all was passed, ignoring --file and ID/FSID arguments')
268 json_configs
= glob
.glob('{}/*.json'.format(json_dir
))
269 for json_config
in json_configs
:
270 mlogger
.info('activating OSD specified in {}'.format(json_config
))
271 args
.json_config
= json_config
275 json_config
= args
.file
277 json_config
= os
.path
.join(json_dir
, '%s-%s.json' % (args
.osd_id
, args
.osd_fsid
))
278 if not os
.path
.exists(json_config
):
279 raise RuntimeError('Expected JSON config path not found: %s' % json_config
)
280 args
.json_config
= json_config