]> git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/devices/simple/activate.py
import ceph 12.2.12
[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 logger.warning('"type" was not defined, will assume "bluestore"')
39 objectstore = 'bluestore'
40
41 # Go through all the device combinations that are absolutely required,
42 # raise an error describing what was expected and what was found
43 # otherwise.
44 if objectstore == 'filestore':
45 if {'data', 'journal'}.issubset(set(devices)):
46 return True
47 else:
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')
52 else:
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)):
56 return True
57 else:
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')
63
64 def get_device(self, uuid):
65 """
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
70
71 :param uuid: The partition uuid of the device (PARTUUID)
72 """
73 device = disk.get_device_from_partuuid(uuid)
74
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:
79 return device
80
81 if self.encryption_type == 'luks':
82 encryption_utils.luks_open(self.dmcrypt_secret, device, uuid)
83 else:
84 encryption_utils.plain_open(self.dmcrypt_secret, device, uuid)
85
86 return '/dev/mapper/%s' % uuid
87
88 def enable_systemd_units(self, osd_id, osd_fsid):
89 """
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.
95 """
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
100
101 # enable the ceph-volume unit for this OSD
102 systemctl.enable_volume(osd_id, osd_fsid, 'simple')
103
104 # disable any/all ceph-disk units
105 systemctl.mask_ceph_disk()
106 terminal.warning(
107 ('All ceph-disk systemd units have been disabled to '
108 'prevent OSDs getting triggered by UDEV events')
109 )
110 else:
111 terminal.info('Skipping enabling of `simple` systemd unit')
112 terminal.info('Skipping masking of ceph-disk systemd units')
113
114 if not self.skip_systemd:
115 # enable the OSD
116 systemctl.enable_osd(osd_id)
117
118 # start the OSD
119 systemctl.start_osd(osd_id)
120 else:
121 terminal.info(
122 'Skipping enabling and starting OSD simple systemd unit because --no-systemd was used'
123 )
124
125 @decorators.needs_root
126 def activate(self, args):
127 with open(args.json_config, 'r') as fp:
128 osd_metadata = json.load(fp)
129
130 # Make sure that required devices are configured
131 self.validate_devices(osd_metadata)
132
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')
137 if not data_uuid:
138 raise RuntimeError(
139 'Unable to activate OSD %s - no "uuid" key found for data' % args.osd_id
140 )
141
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)
158
159 cluster_name = osd_metadata.get('cluster_name', 'ceph')
160 osd_dir = '/var/lib/ceph/osd/%s-%s' % (cluster_name, osd_id)
161
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'))
168
169 if not system.device_is_mounted(data_device, destination=osd_dir):
170 process.run(['mount', '-v', data_device, osd_dir])
171
172 device_map = {
173 'journal': journal_device,
174 'block': block_device,
175 'block.db': block_db_device,
176 'block.wal': block_wal_device
177 }
178
179 for name, device in device_map.items():
180 if not device:
181 continue
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])
186
187 # make sure that the journal has proper permissions
188 system.chown(device)
189
190 self.enable_systemd_units(osd_id, osd_fsid)
191
192 terminal.success('Successfully activated OSD %s with FSID %s' % (osd_id, osd_fsid))
193
194 def main(self):
195 sub_command_help = dedent("""
196 Activate OSDs by mounting devices previously configured to their
197 appropriate destination::
198
199 ceph-volume simple activate {ID} {FSID}
200
201 Or using a JSON file directly::
202
203 ceph-volume simple activate --file /etc/ceph/osd/{ID}-{FSID}.json
204
205 The OSD must have been "scanned" previously (see ``ceph-volume simple
206 scan``), so that all needed OSD device information and metadata exist.
207
208 A previously scanned OSD would exist like::
209
210 /etc/ceph/osd/{ID}-{FSID}.json
211
212
213 Environment variables supported:
214
215 CEPH_VOLUME_SIMPLE_JSON_DIR: Directory location for scanned OSD JSON configs
216 """)
217 parser = argparse.ArgumentParser(
218 prog='ceph-volume simple activate',
219 formatter_class=argparse.RawDescriptionHelpFormatter,
220 description=sub_command_help,
221 )
222 parser.add_argument(
223 'osd_id',
224 metavar='ID',
225 nargs='?',
226 help='The ID of the OSD, usually an integer, like 0'
227 )
228 parser.add_argument(
229 'osd_fsid',
230 metavar='FSID',
231 nargs='?',
232 help='The FSID of the OSD, similar to a SHA1'
233 )
234 parser.add_argument(
235 '--all',
236 help='Activate all OSDs with a OSD JSON config',
237 action='store_true',
238 default=False,
239 )
240 parser.add_argument(
241 '--file',
242 help='The path to a JSON file, from a scanned OSD'
243 )
244 parser.add_argument(
245 '--no-systemd',
246 dest='skip_systemd',
247 action='store_true',
248 help='Skip creating and enabling systemd units and starting OSD services',
249 )
250 if len(self.argv) == 0:
251 print(sub_command_help)
252 return
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/')
265 if args.all:
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
272 self.activate(args)
273 else:
274 if args.file:
275 json_config = args.file
276 else:
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
281 self.activate(args)