Merge pull request #18513 from ceph/ceph-volume-zap

ceph-volume: adds the ceph-volume lvm zap subcommand

Reviewed-by: Alfredo Deza <adeza@redhat.com>
This commit is contained in:
Alfredo Deza 2017-10-30 10:57:33 -04:00 committed by GitHub
commit 9421c87944
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 200 additions and 3 deletions

View File

@ -30,3 +30,4 @@ technologies, including plain disks.
lvm/scan
lvm/systemd
lvm/list
lvm/zap

View File

@ -0,0 +1,19 @@
.. _ceph-volume-lvm-zap:
``zap``
=======
This subcommand is used to zap lvs or partitions that have been used
by ceph OSDs so that they may be reused. If given a path to a logical
volume it must be in the format of vg/lv. Any filesystems present
on the given lv or partition will be removed and all data will be purged.
.. note:: The lv or partition will be kept intact.
Zapping a logical volume::
ceph-volume lvm zap {vg name/lv name}
Zapping a partition::
ceph-volume lvm zap /dev/sdc1

View File

@ -213,6 +213,29 @@ def create_vg(name, *devices):
return vg
def remove_lv(path):
"""
Removes a logical volume given it's absolute path.
Will return True if the lv is successfully removed or
raises a RuntimeError if the removal fails.
"""
stdout, stderr, returncode = process.call(
[
'sudo',
'lvremove',
'-v', # verbose
'-f', # force it
path
],
show_command=True,
terminal_verbose=True,
)
if returncode != 0:
raise RuntimeError("Unable to remove %s".format(path))
return True
def create_lv(name, group, size=None, tags=None):
"""
Create a Logical Volume in a Volume Group. Command looks like::
@ -629,6 +652,14 @@ class Volume(object):
obj['path'] = self.lv_path
return obj
def clear_tags(self):
"""
Removes all tags from the Logical Volume.
"""
for k, v in self.tags.items():
tag = "%s=%s" % (k, v)
process.run(['sudo', 'lvchange', '--deltag', tag, self.lv_path])
def set_tags(self, tags):
"""
:param tags: A dictionary of tag names and values, like::

View File

@ -6,6 +6,7 @@ from . import prepare
from . import create
from . import trigger
from . import listing
from . import zap
class LVM(object):
@ -24,6 +25,7 @@ class LVM(object):
'create': create.Create,
'trigger': trigger.Trigger,
'list': listing.List,
'zap': zap.Zap,
}
def __init__(self, argv):

View File

@ -120,9 +120,13 @@ class Prepare(object):
tags['ceph.%s_uuid' % device_type] = uuid
tags['ceph.%s_device' % device_type] = path
lv.set_tags(tags)
return path, uuid, tags
# otherwise assume this is a regular disk partition
return device_name, self.get_ptuuid(device_name), tags
else:
# otherwise assume this is a regular disk partition
uuid = self.get_ptuuid(device_name)
path = device_name
tags['ceph.%s_uuid' % device_type] = uuid
tags['ceph.%s_device' % device_type] = path
return path, uuid, tags
@decorators.needs_root
def prepare(self, args):

View File

@ -0,0 +1,107 @@
import argparse
import logging
from textwrap import dedent
from ceph_volume import decorators, terminal, process
from ceph_volume.api import lvm as api
logger = logging.getLogger(__name__)
def wipefs(path):
"""
Removes the filesystem from an lv or partition.
"""
process.run([
'sudo',
'wipefs',
'--all',
path
])
def zap_data(path):
"""
Clears all data from the given path. Path should be
an absolute path to an lv or partition.
10M of data is written to the path to make sure that
there is no trace left of any previous Filesystem.
"""
process.run([
'dd',
'if=/dev/zero',
'of={path}'.format(path=path),
'bs=1M',
'count=10',
])
class Zap(object):
help = 'Removes all data and filesystems from a logical volume or partition.'
def __init__(self, argv):
self.argv = argv
@decorators.needs_root
def zap(self, args):
device = args.device
lv = api.get_lv_from_argument(device)
if lv:
# we are zapping a logical volume
path = lv.lv_path
else:
# we are zapping a partition
#TODO: ensure device is a partition
path = device
logger.info("Zapping: %s", path)
terminal.write("Zapping: %s" % path)
wipefs(path)
zap_data(path)
if lv:
# remove all lvm metadata
lv.clear_tags()
terminal.success("Zapping successful for: %s" % path)
def main(self):
sub_command_help = dedent("""
Zaps the given logical volume or partition. If given a path to a logical
volume it must be in the format of vg/lv. Any filesystems present
on the given lv or partition will be removed and all data will be purged.
However, the lv or partition will be kept intact.
Example calls for supported scenarios:
Zapping a logical volume:
ceph-volume lvm zap {vg name/lv name}
Zapping a partition:
ceph-volume lvm zap /dev/sdc1
""")
parser = argparse.ArgumentParser(
prog='ceph-volume lvm zap',
formatter_class=argparse.RawDescriptionHelpFormatter,
description=sub_command_help,
)
parser.add_argument(
'device',
metavar='DEVICE',
nargs='?',
help='Path to an lv (as vg/lv) or to a partition like /dev/sda1'
)
if len(self.argv) == 0:
print(sub_command_help)
return
args = parser.parse_args(self.argv)
self.zap(args)

View File

@ -353,6 +353,22 @@ class TestGetLVFromArgument(object):
assert api.get_lv_from_argument('/path/to/lv') == self.foo_volume
class TestRemoveLV(object):
def test_removes_lv(self, monkeypatch):
def mock_call(cmd, **kw):
return ('', '', 0)
monkeypatch.setattr(process, 'call', mock_call)
assert api.remove_lv("vg/lv")
def test_fails_to_remove_lv(self, monkeypatch):
def mock_call(cmd, **kw):
return ('', '', 1)
monkeypatch.setattr(process, 'call', mock_call)
with pytest.raises(RuntimeError):
api.remove_lv("vg/lv")
class TestCreateLV(object):
def setup(self):

View File

@ -0,0 +1,17 @@
import pytest
from ceph_volume.devices import lvm
class TestZap(object):
def test_main_spits_help_with_no_arguments(self, capsys):
lvm.zap.Zap([]).main()
stdout, stderr = capsys.readouterr()
assert 'Zaps the given logical volume or partition' in stdout
def test_main_shows_full_help(self, capsys):
with pytest.raises(SystemExit):
lvm.zap.Zap(argv=['--help']).main()
stdout, stderr = capsys.readouterr()
assert 'optional arguments' in stdout
assert 'positional arguments' in stdout