1 from __future__
import print_function
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
16 logger
= logging
.getLogger(__name__
)
17 mlogger
= terminal
.MultiLogger(__name__
)
20 class Activate(object):
22 help = 'Enable systemd units to mount configured devices and start a Ceph OSD'
24 def __init__(self
, argv
, from_trigger
=False):
26 self
.from_trigger
= from_trigger
27 self
.skip_systemd
= False
29 def validate_devices(self
, json_config
):
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
35 devices
= json_config
.keys()
37 objectstore
= json_config
['type']
39 if {'data', 'journal'}.issubset(set(devices
)):
41 '"type" key not found, assuming "filestore" since journal key is present'
43 objectstore
= 'filestore'
46 '"type" key not found, assuming "bluestore" since journal key is not present'
48 objectstore
= 'bluestore'
50 # Go through all the device combinations that are absolutely required,
51 # raise an error describing what was expected and what was found
53 if objectstore
== 'filestore':
54 if {'data', 'journal'}.issubset(set(devices
)):
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')
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
)):
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')
73 def get_device(self
, uuid
):
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
80 :param uuid: The partition uuid of the device (PARTUUID)
82 device
= disk
.get_device_from_partuuid(uuid
)
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
:
90 if self
.encryption_type
== 'luks':
91 encryption_utils
.luks_open(self
.dmcrypt_secret
, device
, uuid
)
93 encryption_utils
.plain_open(self
.dmcrypt_secret
, device
, uuid
)
95 return '/dev/mapper/%s' % uuid
97 def enable_systemd_units(self
, osd_id
, osd_fsid
):
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.
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
110 # enable the ceph-volume unit for this OSD
111 systemctl
.enable_volume(osd_id
, osd_fsid
, 'simple')
113 # disable any/all ceph-disk units
114 systemctl
.mask_ceph_disk()
116 ('All ceph-disk systemd units have been disabled to '
117 'prevent OSDs getting triggered by UDEV events')
120 terminal
.info('Skipping enabling of `simple` systemd unit')
121 terminal
.info('Skipping masking of ceph-disk systemd units')
123 if not self
.skip_systemd
:
125 systemctl
.enable_osd(osd_id
)
128 systemctl
.start_osd(osd_id
)
131 'Skipping enabling and starting OSD simple systemd unit because --no-systemd was used'
134 @decorators.needs_root
135 def activate(self
, args
):
136 with
open(args
.json_config
, 'r') as fp
:
137 osd_metadata
= json
.load(fp
)
139 # Make sure that required devices are configured
140 self
.validate_devices(osd_metadata
)
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')
148 'Unable to activate OSD %s - no "uuid" key found for data' % args
.osd_id
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
)
168 cluster_name
= osd_metadata
.get('cluster_name', 'ceph')
169 osd_dir
= '/var/lib/ceph/osd/%s-%s' % (cluster_name
, osd_id
)
171 # XXX there is no support for LVM here
172 data_device
= self
.get_device(data_uuid
)
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
))
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'))
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
)
188 process
.run(['mount', '-v', data_device
, osd_dir
])
191 'journal': journal_device
,
192 'block': block_device
,
193 'block.db': block_db_device
,
194 'block.wal': block_wal_device
197 for name
, device
in device_map
.items():
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
])
205 # make sure that the journal has proper permissions
208 self
.enable_systemd_units(osd_id
, osd_fsid
)
210 terminal
.success('Successfully activated OSD %s with FSID %s' % (osd_id
, osd_fsid
))
213 sub_command_help
= dedent("""
214 Activate OSDs by mounting devices previously configured to their
215 appropriate destination::
217 ceph-volume simple activate {ID} {FSID}
219 Or using a JSON file directly::
221 ceph-volume simple activate --file /etc/ceph/osd/{ID}-{FSID}.json
223 The OSD must have been "scanned" previously (see ``ceph-volume simple
224 scan``), so that all needed OSD device information and metadata exist.
226 A previously scanned OSD would exist like::
228 /etc/ceph/osd/{ID}-{FSID}.json
231 Environment variables supported:
233 CEPH_VOLUME_SIMPLE_JSON_DIR: Directory location for scanned OSD JSON configs
235 parser
= argparse
.ArgumentParser(
236 prog
='ceph-volume simple activate',
237 formatter_class
=argparse
.RawDescriptionHelpFormatter
,
238 description
=sub_command_help
,
244 help='The ID of the OSD, usually an integer, like 0'
250 help='The FSID of the OSD, similar to a SHA1'
254 help='Activate all OSDs with a OSD JSON config',
260 help='The path to a JSON file, from a scanned OSD'
266 help='Skip creating and enabling systemd units and starting OSD services',
268 if len(self
.argv
) == 0:
269 print(sub_command_help
)
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/')
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
292 except RuntimeError as e
:
293 terminal
.warning(e
.message
)
296 json_config
= args
.file
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