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