]> git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/devices/simple/activate.py
Import ceph 15.2.8
[ceph.git] / ceph / src / ceph-volume / ceph_volume / devices / simple / activate.py
1 from __future__ import print_function
2 import argparse
3 import base64
4 import glob
5 import json
6 import logging
7 import os
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.util import prepare as prepare_utils
13 from ceph_volume.systemd import systemctl
14
15
16 logger = logging.getLogger(__name__)
17 mlogger = terminal.MultiLogger(__name__)
18
19
20 class Activate(object):
21
22 help = 'Enable systemd units to mount configured devices and start a Ceph OSD'
23
24 def __init__(self, argv, from_trigger=False):
25 self.argv = argv
26 self.from_trigger = from_trigger
27 self.skip_systemd = False
28
29 def validate_devices(self, json_config):
30 """
31 ``json_config`` is the loaded dictionary coming from the JSON file. It is usually mixed with
32 other non-device items, but for sakes of comparison it doesn't really matter. This method is
33 just making sure that the keys needed exist
34 """
35 devices = json_config.keys()
36 try:
37 objectstore = json_config['type']
38 except KeyError:
39 if {'data', 'journal'}.issubset(set(devices)):
40 logger.warning(
41 '"type" key not found, assuming "filestore" since journal key is present'
42 )
43 objectstore = 'filestore'
44 else:
45 logger.warning(
46 '"type" key not found, assuming "bluestore" since journal key is not present'
47 )
48 objectstore = 'bluestore'
49
50 # Go through all the device combinations that are absolutely required,
51 # raise an error describing what was expected and what was found
52 # otherwise.
53 if objectstore == 'filestore':
54 if {'data', 'journal'}.issubset(set(devices)):
55 return True
56 else:
57 found = [i for i in devices if i in ['data', 'journal']]
58 mlogger.error("Required devices (data, and journal) not present for filestore")
59 mlogger.error('filestore devices found: %s', found)
60 raise RuntimeError('Unable to activate filestore OSD due to missing devices')
61 else:
62 # This is a bit tricky, with newer bluestore we don't need data, older implementations
63 # do (e.g. with ceph-disk). ceph-volume just uses a tmpfs that doesn't require data.
64 if {'block', 'data'}.issubset(set(devices)):
65 return True
66 else:
67 bluestore_devices = ['block.db', 'block.wal', 'block', 'data']
68 found = [i for i in devices if i in bluestore_devices]
69 mlogger.error("Required devices (block and data) not present for bluestore")
70 mlogger.error('bluestore devices found: %s', found)
71 raise RuntimeError('Unable to activate bluestore OSD due to missing devices')
72
73 def get_device(self, uuid):
74 """
75 If a device is encrypted, it will decrypt/open and return the mapper
76 path, if it isn't encrypted it will just return the device found that
77 is mapped to the uuid. This will make it easier for the caller to
78 avoid if/else to check if devices need decrypting
79
80 :param uuid: The partition uuid of the device (PARTUUID)
81 """
82 device = disk.get_device_from_partuuid(uuid)
83
84 # If device is not found, it is fine to return an empty string from the
85 # helper that finds `device`. If it finds anything and it is not
86 # encrypted, just return what was found
87 if not self.is_encrypted or not device:
88 return device
89
90 if self.encryption_type == 'luks':
91 encryption_utils.luks_open(self.dmcrypt_secret, device, uuid)
92 else:
93 encryption_utils.plain_open(self.dmcrypt_secret, device, uuid)
94
95 return '/dev/mapper/%s' % uuid
96
97 def enable_systemd_units(self, osd_id, osd_fsid):
98 """
99 * disables the ceph-disk systemd units to prevent them from running when
100 a UDEV event matches Ceph rules
101 * creates the ``simple`` systemd units to handle the activation and
102 startup of the OSD with ``osd_id`` and ``osd_fsid``
103 * enables the OSD systemd unit and finally starts the OSD.
104 """
105 if not self.from_trigger and not self.skip_systemd:
106 # means it was scanned and now activated directly, so ensure that
107 # ceph-disk units are disabled, and that the `simple` systemd unit
108 # is created and enabled
109
110 # enable the ceph-volume unit for this OSD
111 systemctl.enable_volume(osd_id, osd_fsid, 'simple')
112
113 # disable any/all ceph-disk units
114 systemctl.mask_ceph_disk()
115 terminal.warning(
116 ('All ceph-disk systemd units have been disabled to '
117 'prevent OSDs getting triggered by UDEV events')
118 )
119 else:
120 terminal.info('Skipping enabling of `simple` systemd unit')
121 terminal.info('Skipping masking of ceph-disk systemd units')
122
123 if not self.skip_systemd:
124 # enable the OSD
125 systemctl.enable_osd(osd_id)
126
127 # start the OSD
128 systemctl.start_osd(osd_id)
129 else:
130 terminal.info(
131 'Skipping enabling and starting OSD simple systemd unit because --no-systemd was used'
132 )
133
134 @decorators.needs_root
135 def activate(self, args):
136 with open(args.json_config, 'r') as fp:
137 osd_metadata = json.load(fp)
138
139 # Make sure that required devices are configured
140 self.validate_devices(osd_metadata)
141
142 osd_id = osd_metadata.get('whoami', args.osd_id)
143 osd_fsid = osd_metadata.get('fsid', args.osd_fsid)
144 data_uuid = osd_metadata.get('data', {}).get('uuid')
145 conf.cluster = osd_metadata.get('cluster_name', 'ceph')
146 if not data_uuid:
147 raise RuntimeError(
148 'Unable to activate OSD %s - no "uuid" key found for data' % args.osd_id
149 )
150
151 # Encryption detection, and capturing of the keys to decrypt
152 self.is_encrypted = osd_metadata.get('encrypted', False)
153 self.encryption_type = osd_metadata.get('encryption_type')
154 if self.is_encrypted:
155 lockbox_secret = osd_metadata.get('lockbox.keyring')
156 # write the keyring always so that we can unlock
157 encryption_utils.write_lockbox_keyring(osd_id, osd_fsid, lockbox_secret)
158 # Store the secret around so that the decrypt method can reuse
159 raw_dmcrypt_secret = encryption_utils.get_dmcrypt_key(osd_id, osd_fsid)
160 # Note how both these calls need b64decode. For some reason, the
161 # way ceph-disk creates these keys, it stores them in the monitor
162 # *undecoded*, requiring this decode call again. The lvm side of
163 # encryption doesn't need it, so we are assuming here that anything
164 # that `simple` scans, will come from ceph-disk and will need this
165 # extra decode call here
166 self.dmcrypt_secret = base64.b64decode(raw_dmcrypt_secret)
167
168 cluster_name = osd_metadata.get('cluster_name', 'ceph')
169 osd_dir = '/var/lib/ceph/osd/%s-%s' % (cluster_name, osd_id)
170
171 # XXX there is no support for LVM here
172 data_device = self.get_device(data_uuid)
173
174 if not data_device:
175 raise RuntimeError("osd fsid {} doesn't exist, this file will "
176 "be skipped, consider cleaning legacy "
177 "json file {}".format(osd_metadata['fsid'], args.json_config))
178
179 journal_device = self.get_device(osd_metadata.get('journal', {}).get('uuid'))
180 block_device = self.get_device(osd_metadata.get('block', {}).get('uuid'))
181 block_db_device = self.get_device(osd_metadata.get('block.db', {}).get('uuid'))
182 block_wal_device = self.get_device(osd_metadata.get('block.wal', {}).get('uuid'))
183
184 if not system.device_is_mounted(data_device, destination=osd_dir):
185 if osd_metadata.get('type') == 'filestore':
186 prepare_utils.mount_osd(data_device, osd_id)
187 else:
188 process.run(['mount', '-v', data_device, osd_dir])
189
190 device_map = {
191 'journal': journal_device,
192 'block': block_device,
193 'block.db': block_db_device,
194 'block.wal': block_wal_device
195 }
196
197 for name, device in device_map.items():
198 if not device:
199 continue
200 # always re-do the symlink regardless if it exists, so that the journal
201 # device path that may have changed can be mapped correctly every time
202 destination = os.path.join(osd_dir, name)
203 process.run(['ln', '-snf', device, destination])
204
205 # make sure that the journal has proper permissions
206 system.chown(device)
207
208 self.enable_systemd_units(osd_id, osd_fsid)
209
210 terminal.success('Successfully activated OSD %s with FSID %s' % (osd_id, osd_fsid))
211
212 def main(self):
213 sub_command_help = dedent("""
214 Activate OSDs by mounting devices previously configured to their
215 appropriate destination::
216
217 ceph-volume simple activate {ID} {FSID}
218
219 Or using a JSON file directly::
220
221 ceph-volume simple activate --file /etc/ceph/osd/{ID}-{FSID}.json
222
223 The OSD must have been "scanned" previously (see ``ceph-volume simple
224 scan``), so that all needed OSD device information and metadata exist.
225
226 A previously scanned OSD would exist like::
227
228 /etc/ceph/osd/{ID}-{FSID}.json
229
230
231 Environment variables supported:
232
233 CEPH_VOLUME_SIMPLE_JSON_DIR: Directory location for scanned OSD JSON configs
234 """)
235 parser = argparse.ArgumentParser(
236 prog='ceph-volume simple activate',
237 formatter_class=argparse.RawDescriptionHelpFormatter,
238 description=sub_command_help,
239 )
240 parser.add_argument(
241 'osd_id',
242 metavar='ID',
243 nargs='?',
244 help='The ID of the OSD, usually an integer, like 0'
245 )
246 parser.add_argument(
247 'osd_fsid',
248 metavar='FSID',
249 nargs='?',
250 help='The FSID of the OSD, similar to a SHA1'
251 )
252 parser.add_argument(
253 '--all',
254 help='Activate all OSDs with a OSD JSON config',
255 action='store_true',
256 default=False,
257 )
258 parser.add_argument(
259 '--file',
260 help='The path to a JSON file, from a scanned OSD'
261 )
262 parser.add_argument(
263 '--no-systemd',
264 dest='skip_systemd',
265 action='store_true',
266 help='Skip creating and enabling systemd units and starting OSD services',
267 )
268 if len(self.argv) == 0:
269 print(sub_command_help)
270 return
271 args = parser.parse_args(self.argv)
272 if not args.file and not args.all:
273 if not args.osd_id and not args.osd_fsid:
274 terminal.error('ID and FSID are required to find the right OSD to activate')
275 terminal.error('from a scanned OSD location in /etc/ceph/osd/')
276 raise RuntimeError('Unable to activate without both ID and FSID')
277 # don't allow a CLI flag to specify the JSON dir, because that might
278 # implicitly indicate that it would be possible to activate a json file
279 # at a non-default location which would not work at boot time if the
280 # custom location is not exposed through an ENV var
281 self.skip_systemd = args.skip_systemd
282 json_dir = os.environ.get('CEPH_VOLUME_SIMPLE_JSON_DIR', '/etc/ceph/osd/')
283 if args.all:
284 if args.file or args.osd_id:
285 mlogger.warn('--all was passed, ignoring --file and ID/FSID arguments')
286 json_configs = glob.glob('{}/*.json'.format(json_dir))
287 for json_config in json_configs:
288 mlogger.info('activating OSD specified in {}'.format(json_config))
289 args.json_config = json_config
290 try:
291 self.activate(args)
292 except RuntimeError as e:
293 terminal.warning(e.message)
294 else:
295 if args.file:
296 json_config = args.file
297 else:
298 json_config = os.path.join(json_dir, '%s-%s.json' % (args.osd_id, args.osd_fsid))
299 if not os.path.exists(json_config):
300 raise RuntimeError('Expected JSON config path not found: %s' % json_config)
301 args.json_config = json_config
302 self.activate(args)