1 from __future__
import print_function
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
14 logger
= logging
.getLogger(__name__
)
15 mlogger
= terminal
.MultiLogger(__name__
)
18 class Activate(object):
20 help = 'Enable systemd units to mount configured devices and start a Ceph OSD'
22 def __init__(self
, argv
, from_trigger
=False):
24 self
.from_trigger
= from_trigger
25 self
.skip_systemd
= False
27 def validate_devices(self
, json_config
):
29 ``json_config`` is the loaded dictionary coming from the JSON file. It is usually mixed with
30 other non-device items, but for sakes of comparison it doesn't really matter. This method is
31 just making sure that the keys needed exist
33 devices
= json_config
.keys()
35 objectstore
= json_config
['type']
37 logger
.warning('"type" was not defined, will assume "bluestore"')
38 objectstore
= 'bluestore'
40 # Go through all the device combinations that are absolutely required,
41 # raise an error describing what was expected and what was found
43 if objectstore
== 'filestore':
44 if {'data', 'journal'}.issubset(set(devices
)):
47 found
= [i
for i
in devices
if i
in ['data', 'journal']]
48 mlogger
.error("Required devices (data, and journal) not present for filestore")
49 mlogger
.error('filestore devices found: %s', found
)
50 raise RuntimeError('Unable to activate filestore OSD due to missing devices')
52 # This is a bit tricky, with newer bluestore we don't need data, older implementations
53 # do (e.g. with ceph-disk). ceph-volume just uses a tmpfs that doesn't require data.
54 if {'block', 'data'}.issubset(set(devices
)):
57 bluestore_devices
= ['block.db', 'block.wal', 'block', 'data']
58 found
= [i
for i
in devices
if i
in bluestore_devices
]
59 mlogger
.error("Required devices (block and data) not present for bluestore")
60 mlogger
.error('bluestore devices found: %s', found
)
61 raise RuntimeError('Unable to activate bluestore OSD due to missing devices')
63 def get_device(self
, uuid
):
65 If a device is encrypted, it will decrypt/open and return the mapper
66 path, if it isn't encrypted it will just return the device found that
67 is mapped to the uuid. This will make it easier for the caller to
68 avoid if/else to check if devices need decrypting
70 :param uuid: The partition uuid of the device (PARTUUID)
72 device
= disk
.get_device_from_partuuid(uuid
)
74 # If device is not found, it is fine to return an empty string from the
75 # helper that finds `device`. If it finds anything and it is not
76 # encrypted, just return what was found
77 if not self
.is_encrypted
or not device
:
80 if self
.encryption_type
== 'luks':
81 encryption_utils
.luks_open(self
.dmcrypt_secret
, device
, uuid
)
83 encryption_utils
.plain_open(self
.dmcrypt_secret
, device
, uuid
)
85 return '/dev/mapper/%s' % uuid
87 def enable_systemd_units(self
, osd_id
, osd_fsid
):
89 * disables the ceph-disk systemd units to prevent them from running when
90 a UDEV event matches Ceph rules
91 * creates the ``simple`` systemd units to handle the activation and
92 startup of the OSD with ``osd_id`` and ``osd_fsid``
93 * enables the OSD systemd unit and finally starts the OSD.
95 if not self
.from_trigger
and not self
.skip_systemd
:
96 # means it was scanned and now activated directly, so ensure that
97 # ceph-disk units are disabled, and that the `simple` systemd unit
98 # is created and enabled
100 # enable the ceph-volume unit for this OSD
101 systemctl
.enable_volume(osd_id
, osd_fsid
, 'simple')
103 # disable any/all ceph-disk units
104 systemctl
.mask_ceph_disk()
106 ('All ceph-disk systemd units have been disabled to '
107 'prevent OSDs getting triggered by UDEV events')
110 terminal
.info('Skipping enabling of `simple` systemd unit')
111 terminal
.info('Skipping masking of ceph-disk systemd units')
113 if not self
.skip_systemd
:
115 systemctl
.enable_osd(osd_id
)
118 systemctl
.start_osd(osd_id
)
121 'Skipping enabling and starting OSD simple systemd unit because --no-systemd was used'
124 @decorators.needs_root
125 def activate(self
, args
):
126 with
open(args
.json_config
, 'r') as fp
:
127 osd_metadata
= json
.load(fp
)
129 # Make sure that required devices are configured
130 self
.validate_devices(osd_metadata
)
132 osd_id
= osd_metadata
.get('whoami', args
.osd_id
)
133 osd_fsid
= osd_metadata
.get('fsid', args
.osd_fsid
)
134 data_uuid
= osd_metadata
.get('data', {}).get('uuid')
135 conf
.cluster
= osd_metadata
.get('cluster_name', 'ceph')
138 'Unable to activate OSD %s - no "uuid" key found for data' % args
.osd_id
141 # Encryption detection, and capturing of the keys to decrypt
142 self
.is_encrypted
= osd_metadata
.get('encrypted', False)
143 self
.encryption_type
= osd_metadata
.get('encryption_type')
144 if self
.is_encrypted
:
145 lockbox_secret
= osd_metadata
.get('lockbox.keyring')
146 # write the keyring always so that we can unlock
147 encryption_utils
.write_lockbox_keyring(osd_id
, osd_fsid
, lockbox_secret
)
148 # Store the secret around so that the decrypt method can reuse
149 raw_dmcrypt_secret
= encryption_utils
.get_dmcrypt_key(osd_id
, osd_fsid
)
150 # Note how both these calls need b64decode. For some reason, the
151 # way ceph-disk creates these keys, it stores them in the monitor
152 # *undecoded*, requiring this decode call again. The lvm side of
153 # encryption doesn't need it, so we are assuming here that anything
154 # that `simple` scans, will come from ceph-disk and will need this
155 # extra decode call here
156 self
.dmcrypt_secret
= base64
.b64decode(raw_dmcrypt_secret
)
158 cluster_name
= osd_metadata
.get('cluster_name', 'ceph')
159 osd_dir
= '/var/lib/ceph/osd/%s-%s' % (cluster_name
, osd_id
)
161 # XXX there is no support for LVM here
162 data_device
= self
.get_device(data_uuid
)
163 journal_device
= self
.get_device(osd_metadata
.get('journal', {}).get('uuid'))
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'))
168 if not system
.device_is_mounted(data_device
, destination
=osd_dir
):
169 process
.run(['mount', '-v', data_device
, osd_dir
])
172 'journal': journal_device
,
173 'block': block_device
,
174 'block.db': block_db_device
,
175 'block.wal': block_wal_device
178 for name
, device
in device_map
.items():
181 # always re-do the symlink regardless if it exists, so that the journal
182 # device path that may have changed can be mapped correctly every time
183 destination
= os
.path
.join(osd_dir
, name
)
184 process
.run(['ln', '-snf', device
, destination
])
186 # make sure that the journal has proper permissions
189 self
.enable_systemd_units(osd_id
, osd_fsid
)
191 terminal
.success('Successfully activated OSD %s with FSID %s' % (osd_id
, osd_fsid
))
194 sub_command_help
= dedent("""
195 Activate OSDs by mounting devices previously configured to their
196 appropriate destination::
198 ceph-volume simple activate {ID} {FSID}
200 Or using a JSON file directly::
202 ceph-volume simple activate --file /etc/ceph/osd/{ID}-{FSID}.json
204 The OSD must have been "scanned" previously (see ``ceph-volume simple
205 scan``), so that all needed OSD device information and metadata exist.
207 A previously scanned OSD would exist like::
209 /etc/ceph/osd/{ID}-{FSID}.json
212 Environment variables supported:
214 CEPH_VOLUME_SIMPLE_JSON_DIR: Directory location for scanned OSD JSON configs
216 parser
= argparse
.ArgumentParser(
217 prog
='ceph-volume simple activate',
218 formatter_class
=argparse
.RawDescriptionHelpFormatter
,
219 description
=sub_command_help
,
225 help='The ID of the OSD, usually an integer, like 0'
231 help='The FSID of the OSD, similar to a SHA1'
235 help='The path to a JSON file, from a scanned OSD'
241 help='Skip creating and enabling systemd units and starting OSD services',
243 if len(self
.argv
) == 0:
244 print(sub_command_help
)
246 args
= parser
.parse_args(self
.argv
)
248 if not args
.osd_id
and not args
.osd_fsid
:
249 terminal
.error('ID and FSID are required to find the right OSD to activate')
250 terminal
.error('from a scanned OSD location in /etc/ceph/osd/')
251 raise RuntimeError('Unable to activate without both ID and FSID')
252 # don't allow a CLI flag to specify the JSON dir, because that might
253 # implicitly indicate that it would be possible to activate a json file
254 # at a non-default location which would not work at boot time if the
255 # custom location is not exposed through an ENV var
256 json_dir
= os
.environ
.get('CEPH_VOLUME_SIMPLE_JSON_DIR', '/etc/ceph/osd/')
258 json_config
= args
.file
260 json_config
= os
.path
.join(json_dir
, '%s-%s.json' % (args
.osd_id
, args
.osd_fsid
))
261 if not os
.path
.exists(json_config
):
262 raise RuntimeError('Expected JSON config path not found: %s' % json_config
)
263 args
.json_config
= json_config
264 self
.skip_systemd
= args
.skip_systemd