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
.systemd
import systemctl
15 logger
= logging
.getLogger(__name__
)
16 mlogger
= terminal
.MultiLogger(__name__
)
19 class Activate(object):
21 help = 'Enable systemd units to mount configured devices and start a Ceph OSD'
23 def __init__(self
, argv
, from_trigger
=False):
25 self
.from_trigger
= from_trigger
26 self
.skip_systemd
= False
28 def validate_devices(self
, json_config
):
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
34 devices
= json_config
.keys()
36 objectstore
= json_config
['type']
38 if {'data', 'journal'}.issubset(set(devices
)):
40 '"type" key not found, assuming "filestore" since journal key is present'
42 objectstore
= 'filestore'
45 '"type" key not found, assuming "bluestore" since journal key is not present'
47 objectstore
= 'bluestore'
49 # Go through all the device combinations that are absolutely required,
50 # raise an error describing what was expected and what was found
52 if objectstore
== 'filestore':
53 if {'data', 'journal'}.issubset(set(devices
)):
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')
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
)):
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')
72 def get_device(self
, uuid
):
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
79 :param uuid: The partition uuid of the device (PARTUUID)
81 device
= disk
.get_device_from_partuuid(uuid
)
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
:
89 if self
.encryption_type
== 'luks':
90 encryption_utils
.luks_open(self
.dmcrypt_secret
, device
, uuid
)
92 encryption_utils
.plain_open(self
.dmcrypt_secret
, device
, uuid
)
94 return '/dev/mapper/%s' % uuid
96 def enable_systemd_units(self
, osd_id
, osd_fsid
):
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.
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
109 # enable the ceph-volume unit for this OSD
110 systemctl
.enable_volume(osd_id
, osd_fsid
, 'simple')
112 # disable any/all ceph-disk units
113 systemctl
.mask_ceph_disk()
115 ('All ceph-disk systemd units have been disabled to '
116 'prevent OSDs getting triggered by UDEV events')
119 terminal
.info('Skipping enabling of `simple` systemd unit')
120 terminal
.info('Skipping masking of ceph-disk systemd units')
122 if not self
.skip_systemd
:
124 systemctl
.enable_osd(osd_id
)
127 systemctl
.start_osd(osd_id
)
130 'Skipping enabling and starting OSD simple systemd unit because --no-systemd was used'
133 @decorators.needs_root
134 def activate(self
, args
):
135 with
open(args
.json_config
, 'r') as fp
:
136 osd_metadata
= json
.load(fp
)
138 # Make sure that required devices are configured
139 self
.validate_devices(osd_metadata
)
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')
147 'Unable to activate OSD %s - no "uuid" key found for data' % args
.osd_id
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
)
167 cluster_name
= osd_metadata
.get('cluster_name', 'ceph')
168 osd_dir
= '/var/lib/ceph/osd/%s-%s' % (cluster_name
, osd_id
)
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'))
177 if not system
.device_is_mounted(data_device
, destination
=osd_dir
):
178 process
.run(['mount', '-v', data_device
, osd_dir
])
181 'journal': journal_device
,
182 'block': block_device
,
183 'block.db': block_db_device
,
184 'block.wal': block_wal_device
187 for name
, device
in device_map
.items():
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
])
195 # make sure that the journal has proper permissions
198 self
.enable_systemd_units(osd_id
, osd_fsid
)
200 terminal
.success('Successfully activated OSD %s with FSID %s' % (osd_id
, osd_fsid
))
203 sub_command_help
= dedent("""
204 Activate OSDs by mounting devices previously configured to their
205 appropriate destination::
207 ceph-volume simple activate {ID} {FSID}
209 Or using a JSON file directly::
211 ceph-volume simple activate --file /etc/ceph/osd/{ID}-{FSID}.json
213 The OSD must have been "scanned" previously (see ``ceph-volume simple
214 scan``), so that all needed OSD device information and metadata exist.
216 A previously scanned OSD would exist like::
218 /etc/ceph/osd/{ID}-{FSID}.json
221 Environment variables supported:
223 CEPH_VOLUME_SIMPLE_JSON_DIR: Directory location for scanned OSD JSON configs
225 parser
= argparse
.ArgumentParser(
226 prog
='ceph-volume simple activate',
227 formatter_class
=argparse
.RawDescriptionHelpFormatter
,
228 description
=sub_command_help
,
234 help='The ID of the OSD, usually an integer, like 0'
240 help='The FSID of the OSD, similar to a SHA1'
244 help='Activate all OSDs with a OSD JSON config',
250 help='The path to a JSON file, from a scanned OSD'
256 help='Skip creating and enabling systemd units and starting OSD services',
258 if len(self
.argv
) == 0:
259 print(sub_command_help
)
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/')
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
283 json_config
= args
.file
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