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