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