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