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
, systemd
=False):
24 self
.systemd
= systemd
26 def validate_devices(self
, json_config
):
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
32 devices
= json_config
.keys()
34 objectstore
= json_config
['type']
36 logger
.warning('"type" was not defined, will assume "bluestore"')
37 objectstore
= 'bluestore'
39 # Go through all the device combinations that are absolutely required,
40 # raise an error describing what was expected and what was found
42 if objectstore
== 'filestore':
43 if {'data', 'journal'}.issubset(set(devices
)):
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')
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
)):
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')
62 def get_device(self
, uuid
):
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
69 :param uuid: The partition uuid of the device (PARTUUID)
71 device
= disk
.get_device_from_partuuid(uuid
)
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
:
79 if self
.encryption_type
== 'luks':
80 encryption_utils
.luks_open(self
.dmcrypt_secret
, device
, uuid
)
82 encryption_utils
.plain_open(self
.dmcrypt_secret
, device
, uuid
)
84 return '/dev/mapper/%s' % uuid
86 @decorators.needs_root
87 def activate(self
, args
):
88 with
open(args
.json_config
, 'r') as fp
:
89 osd_metadata
= json
.load(fp
)
91 # Make sure that required devices are configured
92 self
.validate_devices(osd_metadata
)
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')
100 'Unable to activate OSD %s - no "uuid" key found for data' % args
.osd_id
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
)
120 cluster_name
= osd_metadata
.get('cluster_name', 'ceph')
121 osd_dir
= '/var/lib/ceph/osd/%s-%s' % (cluster_name
, osd_id
)
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'))
130 if not system
.device_is_mounted(data_device
, destination
=osd_dir
):
131 process
.run(['mount', '-v', data_device
, osd_dir
])
134 'journal': journal_device
,
135 'block': block_device
,
136 'block.db': block_db_device
,
137 'block.wal': block_wal_device
140 for name
, device
in device_map
.items():
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
])
148 # make sure that the journal has proper permissions
152 # enable the ceph-volume unit for this OSD
153 systemctl
.enable_volume(osd_id
, osd_fsid
, 'simple')
155 # disable any/all ceph-disk units
156 systemctl
.mask_ceph_disk()
159 systemctl
.enable_osd(osd_id
)
162 systemctl
.start_osd(osd_id
)
164 terminal
.success('Successfully activated OSD %s with FSID %s' % (osd_id
, osd_fsid
))
166 ('All ceph-disk systemd units have been disabled to '
167 'prevent OSDs getting triggered by UDEV events')
171 sub_command_help
= dedent("""
172 Activate OSDs by mounting devices previously configured to their
173 appropriate destination::
175 ceph-volume simple activate {ID} {FSID}
177 Or using a JSON file directly::
179 ceph-volume simple activate --file /etc/ceph/osd/{ID}-{FSID}.json
181 The OSD must have been "scanned" previously (see ``ceph-volume simple
182 scan``), so that all needed OSD device information and metadata exist.
184 A previously scanned OSD would exist like::
186 /etc/ceph/osd/{ID}-{FSID}.json
189 Environment variables supported:
191 CEPH_VOLUME_SIMPLE_JSON_DIR: Directory location for scanned OSD JSON configs
193 parser
= argparse
.ArgumentParser(
194 prog
='ceph-volume simple activate',
195 formatter_class
=argparse
.RawDescriptionHelpFormatter
,
196 description
=sub_command_help
,
202 help='The ID of the OSD, usually an integer, like 0'
208 help='The FSID of the OSD, similar to a SHA1'
212 help='The path to a JSON file, from a scanned OSD'
214 if len(self
.argv
) == 0:
215 print(sub_command_help
)
217 args
= parser
.parse_args(self
.argv
)
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/')
229 json_config
= args
.file
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