]> git.proxmox.com Git - ceph.git/blame - ceph/src/ceph-volume/ceph_volume/devices/lvm/zap.py
bump version to 15.2.4-pve1
[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
eafe8130 10from ceph_volume.util import system, encryption, disk, arg_validators, str_to_int
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
84 lvs = api.Volumes()
85 lvs.filter(lv_tags=lv_tags)
86 if not lvs:
87 raise RuntimeError('Unable to find any LV for zapping OSD: %s' % osd_id or osd_fsid)
88
89 devices_to_zap = ensure_associated_lvs(lvs)
90
91 return [Device(path) for path in set(devices_to_zap) if path]
92
93
94def ensure_associated_lvs(lvs):
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
103 journal_lvs = lvs._filter(lv_tags={'ceph.type': 'journal'})
104 db_lvs = lvs._filter(lv_tags={'ceph.type': 'db'})
105 wal_lvs = lvs._filter(lv_tags={'ceph.type': 'wal'})
106 backing_devices = [
107 (journal_lvs, 'journal'),
eafe8130 108 (db_lvs, 'db'),
f64942e4
AA
109 (wal_lvs, 'wal')
110 ]
111
112 verified_devices = []
113
114 for lv in lvs:
115 # go through each lv and append it, otherwise query `blkid` to find
116 # a physical device. Do this for each type (journal,db,wal) regardless
117 # if they have been processed in the previous LV, so that bad devices
118 # with the same ID can be caught
119 for ceph_lvs, _type in backing_devices:
120 if ceph_lvs:
121 verified_devices.extend([l.lv_path for l in ceph_lvs])
122 continue
123
124 # must be a disk partition, by querying blkid by the uuid we are
125 # ensuring that the device path is always correct
126 try:
127 device_uuid = lv.tags['ceph.%s_uuid' % _type]
128 except KeyError:
129 # Bluestore will not have ceph.journal_uuid, and Filestore
130 # will not not have ceph.db_uuid
131 continue
132
133 osd_device = disk.get_device_from_partuuid(device_uuid)
134 if not osd_device:
135 # if the osd_device is not found by the partuuid, then it is
136 # not possible to ensure this device exists anymore, so skip it
137 continue
138 verified_devices.append(osd_device)
139
140 verified_devices.append(lv.lv_path)
141
142 # reduce the list from all the duplicates that were added
143 return list(set(verified_devices))
144
145
3efd9988
FG
146class Zap(object):
147
148 help = 'Removes all data and filesystems from a logical volume or partition.'
149
150 def __init__(self, argv):
151 self.argv = argv
152
1adf2230
AA
153 def unmount_lv(self, lv):
154 if lv.tags.get('ceph.cluster_name') and lv.tags.get('ceph.osd_id'):
155 lv_path = "/var/lib/ceph/osd/{}-{}".format(lv.tags['ceph.cluster_name'], lv.tags['ceph.osd_id'])
3a9019d9 156 else:
1adf2230
AA
157 lv_path = lv.lv_path
158 dmcrypt_uuid = lv.lv_uuid
159 dmcrypt = lv.encrypted
160 if system.path_is_mounted(lv_path):
161 mlogger.info("Unmounting %s", lv_path)
162 system.unmount(lv_path)
3a9019d9 163 if dmcrypt and dmcrypt_uuid:
1adf2230 164 self.dmcrypt_close(dmcrypt_uuid)
3efd9988 165
f64942e4
AA
166 def zap_lv(self, device):
167 """
168 Device examples: vg-name/lv-name, /dev/vg-name/lv-name
169 Requirements: Must be a logical volume (LV)
170 """
171 lv = api.get_lv(lv_name=device.lv_name, vg_name=device.vg_name)
172 self.unmount_lv(lv)
173
174 wipefs(device.abspath)
175 zap_data(device.abspath)
176
177 if self.args.destroy:
178 lvs = api.Volumes()
179 lvs.filter(vg_name=device.vg_name)
180 if len(lvs) <= 1:
181 mlogger.info('Only 1 LV left in VG, will proceed to destroy volume group %s', device.vg_name)
182 api.remove_vg(device.vg_name)
183 else:
184 mlogger.info('More than 1 LV left in VG, will proceed to destroy LV only')
185 mlogger.info('Removing LV because --destroy was given: %s', device.abspath)
186 api.remove_lv(device.abspath)
187 elif lv:
188 # just remove all lvm metadata, leaving the LV around
189 lv.clear_tags()
190
191 def zap_partition(self, device):
192 """
193 Device example: /dev/sda1
194 Requirements: Must be a partition
195 """
196 if device.is_encrypted:
197 # find the holder
198 holders = [
199 '/dev/%s' % holder for holder in device.sys_api.get('holders', [])
200 ]
201 for mapper_uuid in os.listdir('/dev/mapper'):
202 mapper_path = os.path.join('/dev/mapper', mapper_uuid)
203 if os.path.realpath(mapper_path) in holders:
204 self.dmcrypt_close(mapper_uuid)
205
206 if system.device_is_mounted(device.abspath):
207 mlogger.info("Unmounting %s", device.abspath)
208 system.unmount(device.abspath)
209
210 wipefs(device.abspath)
211 zap_data(device.abspath)
212
213 if self.args.destroy:
214 mlogger.info("Destroying partition since --destroy was used: %s" % device.abspath)
215 disk.remove_partition(device)
216
217 def zap_lvm_member(self, device):
218 """
219 An LVM member may have more than one LV and or VG, for example if it is
220 a raw device with multiple partitions each belonging to a different LV
221
222 Device example: /dev/sda
223 Requirements: An LV or VG present in the device, making it an LVM member
224 """
225 for lv in device.lvs:
92f5a8d4
TL
226 if lv.lv_name:
227 mlogger.info('Zapping lvm member {}. lv_path is {}'.format(device.abspath, lv.lv_path))
228 self.zap_lv(Device(lv.lv_path))
229 else:
230 vg = api.get_first_vg(filters={'vg_name': lv.vg_name})
231 if vg:
232 mlogger.info('Found empty VG {}, removing'.format(vg.vg_name))
233 api.remove_vg(vg.vg_name)
234
f64942e4
AA
235
236
237 def zap_raw_device(self, device):
238 """
239 Any whole (raw) device passed in as input will be processed here,
240 checking for LVM membership and partitions (if any).
241
242 Device example: /dev/sda
243 Requirements: None
244 """
245 if not self.args.destroy:
246 # the use of dd on a raw device causes the partition table to be
247 # destroyed
248 mlogger.warning(
249 '--destroy was not specified, but zapping a whole device will remove the partition table'
250 )
251
252 # look for partitions and zap those
253 for part_name in device.sys_api.get('partitions', {}).keys():
254 self.zap_partition(Device('/dev/%s' % part_name))
255
256 wipefs(device.abspath)
257 zap_data(device.abspath)
258
1adf2230 259 @decorators.needs_root
f64942e4
AA
260 def zap(self, devices=None):
261 devices = devices or self.args.devices
262
263 for device in devices:
264 mlogger.info("Zapping: %s", device.abspath)
265 if device.is_mapper:
1adf2230
AA
266 terminal.error("Refusing to zap the mapper device: {}".format(device))
267 raise SystemExit(1)
f64942e4
AA
268 if device.is_lvm_member:
269 self.zap_lvm_member(device)
270 if device.is_lv:
271 self.zap_lv(device)
272 if device.is_partition:
273 self.zap_partition(device)
274 if device.is_device:
275 self.zap_raw_device(device)
276
277 if self.args.devices:
278 terminal.success(
279 "Zapping successful for: %s" % ", ".join([str(d) for d in self.args.devices])
280 )
281 else:
eafe8130 282 identifier = self.args.osd_id or self.args.osd_fsid
f64942e4 283 terminal.success(
eafe8130 284 "Zapping successful for OSD: %s" % identifier
f64942e4
AA
285 )
286
287 @decorators.needs_root
288 def zap_osd(self):
289 if self.args.osd_id:
290 osd_is_running = systemctl.osd_is_active(self.args.osd_id)
291 if osd_is_running:
292 mlogger.error("OSD ID %s is running, stop it with:" % self.args.osd_id)
293 mlogger.error("systemctl stop ceph-osd@%s" % self.args.osd_id)
294 raise SystemExit("Unable to zap devices associated with OSD ID: %s" % self.args.osd_id)
295 devices = find_associated_devices(self.args.osd_id, self.args.osd_fsid)
296 self.zap(devices)
1adf2230
AA
297
298 def dmcrypt_close(self, dmcrypt_uuid):
299 dmcrypt_path = "/dev/mapper/{}".format(dmcrypt_uuid)
300 mlogger.info("Closing encrypted path %s", dmcrypt_path)
301 encryption.dmcrypt_close(dmcrypt_path)
3efd9988
FG
302
303 def main(self):
304 sub_command_help = dedent("""
1adf2230 305 Zaps the given logical volume(s), raw device(s) or partition(s) for reuse by ceph-volume.
b32b8144
FG
306 If given a path to a logical volume it must be in the format of vg/lv. Any
307 filesystems present on the given device, vg/lv, or partition will be removed and
308 all data will be purged.
309
310 If the logical volume, raw device or partition is being used for any ceph related
311 mount points they will be unmounted.
3efd9988
FG
312
313 However, the lv or partition will be kept intact.
314
315 Example calls for supported scenarios:
316
317 Zapping a logical volume:
318
319 ceph-volume lvm zap {vg name/lv name}
320
321 Zapping a partition:
322
323 ceph-volume lvm zap /dev/sdc1
324
1adf2230
AA
325 Zapping many raw devices:
326
327 ceph-volume lvm zap /dev/sda /dev/sdb /db/sdc
328
f64942e4
AA
329 Zapping devices associated with an OSD ID:
330
331 ceph-volume lvm zap --osd-id 1
332
333 Optionally include the OSD FSID
334
335 ceph-volume lvm zap --osd-id 1 --osd-fsid 55BD4219-16A7-4037-BC20-0F158EFCC83D
336
b32b8144
FG
337 If the --destroy flag is given and you are zapping a raw device or partition
338 then all vgs and lvs that exist on that raw device or partition will be destroyed.
339
340 This is especially useful if a raw device or partition was used by ceph-volume lvm create
341 or ceph-volume lvm prepare commands previously and now you want to reuse that device.
342
343 For example:
344
345 ceph-volume lvm zap /dev/sda --destroy
346
347 If the --destroy flag is given and you are zapping an lv then the lv is still
348 kept intact for reuse.
349
3efd9988
FG
350 """)
351 parser = argparse.ArgumentParser(
352 prog='ceph-volume lvm zap',
353 formatter_class=argparse.RawDescriptionHelpFormatter,
354 description=sub_command_help,
355 )
356
357 parser.add_argument(
1adf2230
AA
358 'devices',
359 metavar='DEVICES',
360 nargs='*',
f64942e4 361 type=arg_validators.ValidDevice(gpt_ok=True),
1adf2230
AA
362 default=[],
363 help='Path to one or many lv (as vg/lv), partition (as /dev/sda1) or device (as /dev/sda)'
b32b8144 364 )
f64942e4 365
b32b8144
FG
366 parser.add_argument(
367 '--destroy',
368 action='store_true',
369 default=False,
370 help='Destroy all volume groups and logical volumes if you are zapping a raw device or partition',
3efd9988 371 )
f64942e4
AA
372
373 parser.add_argument(
374 '--osd-id',
375 help='Specify an OSD ID to detect associated devices for zapping',
376 )
377
378 parser.add_argument(
379 '--osd-fsid',
380 help='Specify an OSD FSID to detect associated devices for zapping',
381 )
382
3efd9988
FG
383 if len(self.argv) == 0:
384 print(sub_command_help)
385 return
f64942e4
AA
386
387 self.args = parser.parse_args(self.argv)
388
389 if self.args.osd_id or self.args.osd_fsid:
390 self.zap_osd()
391 else:
392 self.zap()