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