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