]> git.proxmox.com Git - ceph.git/blame - ceph/src/ceph-volume/ceph_volume/devices/lvm/zap.py
update ceph source to reef 18.2.1
[ceph.git] / ceph / src / ceph-volume / ceph_volume / devices / lvm / zap.py
CommitLineData
3efd9988 1import argparse
f64942e4 2import os
3efd9988 3import logging
eafe8130 4import time
3efd9988
FG
5
6from textwrap import dedent
7
8from ceph_volume import decorators, terminal, process
9from ceph_volume.api import lvm as api
f6b5b4d7 10from ceph_volume.util import system, encryption, disk, arg_validators, str_to_int, merge_dict
f64942e4
AA
11from ceph_volume.util.device import Device
12from ceph_volume.systemd import systemctl
3efd9988
FG
13
14logger = logging.getLogger(__name__)
b32b8144 15mlogger = terminal.MultiLogger(__name__)
3efd9988
FG
16
17
18def wipefs(path):
19 """
20 Removes the filesystem from an lv or partition.
eafe8130
TL
21
22 Environment variables supported::
23
24 * ``CEPH_VOLUME_WIPEFS_TRIES``: Defaults to 8
25 * ``CEPH_VOLUME_WIPEFS_INTERVAL``: Defaults to 5
26
3efd9988 27 """
eafe8130
TL
28 tries = str_to_int(
29 os.environ.get('CEPH_VOLUME_WIPEFS_TRIES', 8)
30 )
31 interval = str_to_int(
32 os.environ.get('CEPH_VOLUME_WIPEFS_INTERVAL', 5)
33 )
34
35 for trying in range(tries):
36 stdout, stderr, exit_code = process.call([
37 'wipefs',
38 '--all',
39 path
40 ])
41 if exit_code != 0:
42 # this could narrow the retry by poking in the stderr of the output
43 # to verify that 'probing initialization failed' appears, but
44 # better to be broad in this retry to prevent missing on
45 # a different message that needs to be retried as well
46 terminal.warning(
47 'failed to wipefs device, will try again to workaround probable race condition'
48 )
49 time.sleep(interval)
50 else:
51 return
52 raise RuntimeError("could not complete wipefs on device: %s" % path)
3efd9988
FG
53
54
55def zap_data(path):
56 """
57 Clears all data from the given path. Path should be
58 an absolute path to an lv or partition.
59
60 10M of data is written to the path to make sure that
61 there is no trace left of any previous Filesystem.
62 """
63 process.run([
64 'dd',
65 'if=/dev/zero',
66 'of={path}'.format(path=path),
67 'bs=1M',
68 'count=10',
92f5a8d4 69 'conv=fsync'
3efd9988
FG
70 ])
71
72
f64942e4
AA
73def find_associated_devices(osd_id=None, osd_fsid=None):
74 """
75 From an ``osd_id`` and/or an ``osd_fsid``, filter out all the LVs in the
76 system that match those tag values, further detect if any partitions are
77 part of the OSD, and then return the set of LVs and partitions (if any).
78 """
79 lv_tags = {}
80 if osd_id:
81 lv_tags['ceph.osd_id'] = osd_id
82 if osd_fsid:
83 lv_tags['ceph.osd_fsid'] = osd_fsid
f64942e4 84
f6b5b4d7
TL
85 lvs = api.get_lvs(tags=lv_tags)
86 if not lvs:
87 raise RuntimeError('Unable to find any LV for zapping OSD: '
88 '%s' % osd_id or osd_fsid)
f64942e4 89
f6b5b4d7 90 devices_to_zap = ensure_associated_lvs(lvs, lv_tags)
f64942e4
AA
91 return [Device(path) for path in set(devices_to_zap) if path]
92
93
f6b5b4d7 94def ensure_associated_lvs(lvs, lv_tags={}):
f64942e4
AA
95 """
96 Go through each LV and ensure if backing devices (journal, wal, block)
97 are LVs or partitions, so that they can be accurately reported.
98 """
99 # look for many LVs for each backing type, because it is possible to
100 # receive a filtering for osd.1, and have multiple failed deployments
101 # leaving many journals with osd.1 - usually, only a single LV will be
102 # returned
f6b5b4d7 103
f6b5b4d7
TL
104 db_lvs = api.get_lvs(tags=merge_dict(lv_tags, {'ceph.type': 'db'}))
105 wal_lvs = api.get_lvs(tags=merge_dict(lv_tags, {'ceph.type': 'wal'}))
05a536ef 106 backing_devices = [(db_lvs, 'db'),
f6b5b4d7 107 (wal_lvs, 'wal')]
f64942e4
AA
108
109 verified_devices = []
110
111 for lv in lvs:
112 # go through each lv and append it, otherwise query `blkid` to find
113 # a physical device. Do this for each type (journal,db,wal) regardless
114 # if they have been processed in the previous LV, so that bad devices
115 # with the same ID can be caught
116 for ceph_lvs, _type in backing_devices:
117 if ceph_lvs:
118 verified_devices.extend([l.lv_path for l in ceph_lvs])
119 continue
120
121 # must be a disk partition, by querying blkid by the uuid we are
122 # ensuring that the device path is always correct
123 try:
124 device_uuid = lv.tags['ceph.%s_uuid' % _type]
125 except KeyError:
126 # Bluestore will not have ceph.journal_uuid, and Filestore
127 # will not not have ceph.db_uuid
128 continue
129
130 osd_device = disk.get_device_from_partuuid(device_uuid)
131 if not osd_device:
132 # if the osd_device is not found by the partuuid, then it is
133 # not possible to ensure this device exists anymore, so skip it
134 continue
135 verified_devices.append(osd_device)
136
137 verified_devices.append(lv.lv_path)
138
139 # reduce the list from all the duplicates that were added
140 return list(set(verified_devices))
141
142
3efd9988
FG
143class Zap(object):
144
145 help = 'Removes all data and filesystems from a logical volume or partition.'
146
147 def __init__(self, argv):
148 self.argv = argv
149
1adf2230
AA
150 def unmount_lv(self, lv):
151 if lv.tags.get('ceph.cluster_name') and lv.tags.get('ceph.osd_id'):
152 lv_path = "/var/lib/ceph/osd/{}-{}".format(lv.tags['ceph.cluster_name'], lv.tags['ceph.osd_id'])
3a9019d9 153 else:
1adf2230
AA
154 lv_path = lv.lv_path
155 dmcrypt_uuid = lv.lv_uuid
156 dmcrypt = lv.encrypted
157 if system.path_is_mounted(lv_path):
158 mlogger.info("Unmounting %s", lv_path)
159 system.unmount(lv_path)
3a9019d9 160 if dmcrypt and dmcrypt_uuid:
1adf2230 161 self.dmcrypt_close(dmcrypt_uuid)
3efd9988 162
f64942e4
AA
163 def zap_lv(self, device):
164 """
165 Device examples: vg-name/lv-name, /dev/vg-name/lv-name
166 Requirements: Must be a logical volume (LV)
167 """
a4b75251
TL
168 lv = api.get_single_lv(filters={'lv_name': device.lv_name, 'vg_name':
169 device.vg_name})
f64942e4
AA
170 self.unmount_lv(lv)
171
2a845540
TL
172 wipefs(device.path)
173 zap_data(device.path)
f64942e4
AA
174
175 if self.args.destroy:
f6b5b4d7
TL
176 lvs = api.get_lvs(filters={'vg_name': device.vg_name})
177 if lvs == []:
178 mlogger.info('No LVs left, exiting', device.vg_name)
179 return
180 elif len(lvs) <= 1:
181 mlogger.info('Only 1 LV left in VG, will proceed to destroy '
182 'volume group %s', device.vg_name)
1e59de90 183 pvs = api.get_pvs(filters={'lv_uuid': lv.lv_uuid})
f64942e4 184 api.remove_vg(device.vg_name)
1e59de90
TL
185 for pv in pvs:
186 api.remove_pv(pv.pv_name)
f64942e4 187 else:
f6b5b4d7
TL
188 mlogger.info('More than 1 LV left in VG, will proceed to '
189 'destroy LV only')
190 mlogger.info('Removing LV because --destroy was given: %s',
2a845540
TL
191 device.path)
192 api.remove_lv(device.path)
f64942e4
AA
193 elif lv:
194 # just remove all lvm metadata, leaving the LV around
195 lv.clear_tags()
196
197 def zap_partition(self, device):
198 """
199 Device example: /dev/sda1
200 Requirements: Must be a partition
201 """
202 if device.is_encrypted:
203 # find the holder
204 holders = [
205 '/dev/%s' % holder for holder in device.sys_api.get('holders', [])
206 ]
207 for mapper_uuid in os.listdir('/dev/mapper'):
208 mapper_path = os.path.join('/dev/mapper', mapper_uuid)
209 if os.path.realpath(mapper_path) in holders:
210 self.dmcrypt_close(mapper_uuid)
211
2a845540
TL
212 if system.device_is_mounted(device.path):
213 mlogger.info("Unmounting %s", device.path)
214 system.unmount(device.path)
f64942e4 215
2a845540
TL
216 wipefs(device.path)
217 zap_data(device.path)
f64942e4
AA
218
219 if self.args.destroy:
2a845540 220 mlogger.info("Destroying partition since --destroy was used: %s" % device.path)
f64942e4
AA
221 disk.remove_partition(device)
222
223 def zap_lvm_member(self, device):
224 """
225 An LVM member may have more than one LV and or VG, for example if it is
226 a raw device with multiple partitions each belonging to a different LV
227
228 Device example: /dev/sda
229 Requirements: An LV or VG present in the device, making it an LVM member
230 """
231 for lv in device.lvs:
92f5a8d4 232 if lv.lv_name:
2a845540 233 mlogger.info('Zapping lvm member {}. lv_path is {}'.format(device.path, lv.lv_path))
92f5a8d4
TL
234 self.zap_lv(Device(lv.lv_path))
235 else:
a4b75251 236 vg = api.get_single_vg(filters={'vg_name': lv.vg_name})
92f5a8d4
TL
237 if vg:
238 mlogger.info('Found empty VG {}, removing'.format(vg.vg_name))
239 api.remove_vg(vg.vg_name)
240
f64942e4
AA
241
242
243 def zap_raw_device(self, device):
244 """
245 Any whole (raw) device passed in as input will be processed here,
246 checking for LVM membership and partitions (if any).
247
248 Device example: /dev/sda
249 Requirements: None
250 """
251 if not self.args.destroy:
252 # the use of dd on a raw device causes the partition table to be
253 # destroyed
254 mlogger.warning(
255 '--destroy was not specified, but zapping a whole device will remove the partition table'
256 )
257
258 # look for partitions and zap those
259 for part_name in device.sys_api.get('partitions', {}).keys():
260 self.zap_partition(Device('/dev/%s' % part_name))
261
2a845540
TL
262 wipefs(device.path)
263 zap_data(device.path)
f64942e4 264
1adf2230 265 @decorators.needs_root
f64942e4
AA
266 def zap(self, devices=None):
267 devices = devices or self.args.devices
268
269 for device in devices:
2a845540 270 mlogger.info("Zapping: %s", device.path)
20effc67 271 if device.is_mapper and not device.is_mpath:
1adf2230
AA
272 terminal.error("Refusing to zap the mapper device: {}".format(device))
273 raise SystemExit(1)
f64942e4
AA
274 if device.is_lvm_member:
275 self.zap_lvm_member(device)
276 if device.is_lv:
277 self.zap_lv(device)
278 if device.is_partition:
279 self.zap_partition(device)
280 if device.is_device:
281 self.zap_raw_device(device)
282
283 if self.args.devices:
284 terminal.success(
285 "Zapping successful for: %s" % ", ".join([str(d) for d in self.args.devices])
286 )
287 else:
eafe8130 288 identifier = self.args.osd_id or self.args.osd_fsid
f64942e4 289 terminal.success(
eafe8130 290 "Zapping successful for OSD: %s" % identifier
f64942e4
AA
291 )
292
293 @decorators.needs_root
294 def zap_osd(self):
f91f0fd5 295 if self.args.osd_id and not self.args.no_systemd:
f64942e4
AA
296 osd_is_running = systemctl.osd_is_active(self.args.osd_id)
297 if osd_is_running:
298 mlogger.error("OSD ID %s is running, stop it with:" % self.args.osd_id)
299 mlogger.error("systemctl stop ceph-osd@%s" % self.args.osd_id)
300 raise SystemExit("Unable to zap devices associated with OSD ID: %s" % self.args.osd_id)
301 devices = find_associated_devices(self.args.osd_id, self.args.osd_fsid)
302 self.zap(devices)
1adf2230
AA
303
304 def dmcrypt_close(self, dmcrypt_uuid):
aee94f69
TL
305 mlogger.info("Closing encrypted volume %s", dmcrypt_uuid)
306 encryption.dmcrypt_close(mapping=dmcrypt_uuid, skip_path_check=True)
3efd9988
FG
307
308 def main(self):
309 sub_command_help = dedent("""
1adf2230 310 Zaps the given logical volume(s), raw device(s) or partition(s) for reuse by ceph-volume.
b32b8144
FG
311 If given a path to a logical volume it must be in the format of vg/lv. Any
312 filesystems present on the given device, vg/lv, or partition will be removed and
313 all data will be purged.
314
315 If the logical volume, raw device or partition is being used for any ceph related
316 mount points they will be unmounted.
3efd9988
FG
317
318 However, the lv or partition will be kept intact.
319
320 Example calls for supported scenarios:
321
322 Zapping a logical volume:
323
324 ceph-volume lvm zap {vg name/lv name}
325
326 Zapping a partition:
327
328 ceph-volume lvm zap /dev/sdc1
329
1adf2230
AA
330 Zapping many raw devices:
331
332 ceph-volume lvm zap /dev/sda /dev/sdb /db/sdc
333
f64942e4
AA
334 Zapping devices associated with an OSD ID:
335
336 ceph-volume lvm zap --osd-id 1
337
338 Optionally include the OSD FSID
339
340 ceph-volume lvm zap --osd-id 1 --osd-fsid 55BD4219-16A7-4037-BC20-0F158EFCC83D
341
b32b8144
FG
342 If the --destroy flag is given and you are zapping a raw device or partition
343 then all vgs and lvs that exist on that raw device or partition will be destroyed.
344
345 This is especially useful if a raw device or partition was used by ceph-volume lvm create
346 or ceph-volume lvm prepare commands previously and now you want to reuse that device.
347
348 For example:
349
350 ceph-volume lvm zap /dev/sda --destroy
351
352 If the --destroy flag is given and you are zapping an lv then the lv is still
353 kept intact for reuse.
354
3efd9988
FG
355 """)
356 parser = argparse.ArgumentParser(
357 prog='ceph-volume lvm zap',
358 formatter_class=argparse.RawDescriptionHelpFormatter,
359 description=sub_command_help,
360 )
361
362 parser.add_argument(
1adf2230
AA
363 'devices',
364 metavar='DEVICES',
365 nargs='*',
33c7a0ef 366 type=arg_validators.ValidZapDevice(gpt_ok=True),
1adf2230
AA
367 default=[],
368 help='Path to one or many lv (as vg/lv), partition (as /dev/sda1) or device (as /dev/sda)'
b32b8144 369 )
f64942e4 370
b32b8144
FG
371 parser.add_argument(
372 '--destroy',
373 action='store_true',
374 default=False,
375 help='Destroy all volume groups and logical volumes if you are zapping a raw device or partition',
3efd9988 376 )
f64942e4
AA
377
378 parser.add_argument(
379 '--osd-id',
20effc67 380 type=arg_validators.valid_osd_id,
f64942e4
AA
381 help='Specify an OSD ID to detect associated devices for zapping',
382 )
383
384 parser.add_argument(
385 '--osd-fsid',
386 help='Specify an OSD FSID to detect associated devices for zapping',
387 )
388
f91f0fd5
TL
389 parser.add_argument(
390 '--no-systemd',
391 dest='no_systemd',
392 action='store_true',
393 help='Skip systemd unit checks',
394 )
395
3efd9988
FG
396 if len(self.argv) == 0:
397 print(sub_command_help)
398 return
f64942e4
AA
399
400 self.args = parser.parse_args(self.argv)
401
402 if self.args.osd_id or self.args.osd_fsid:
403 self.zap_osd()
404 else:
405 self.zap()