volume: Add v3-specific volume snapshot module
Change-Id: I23026abbb909c082fbc0fe0c9b2efcc89f4d464a Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
@ -13,9 +13,12 @@
|
|||||||
|
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
|
from osc_lib.cli import format_columns
|
||||||
from osc_lib import exceptions
|
from osc_lib import exceptions
|
||||||
from osc_lib import utils
|
from osc_lib import utils
|
||||||
|
|
||||||
|
from openstackclient.tests.unit.identity.v3 import fakes as project_fakes
|
||||||
|
from openstackclient.tests.unit import utils as test_utils
|
||||||
from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes
|
from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes
|
||||||
from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes_v3
|
from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes_v3
|
||||||
from openstackclient.volume.v3 import volume_snapshot
|
from openstackclient.volume.v3 import volume_snapshot
|
||||||
@ -27,10 +30,163 @@ class TestVolumeSnapshot(volume_fakes_v3.TestVolume):
|
|||||||
|
|
||||||
self.snapshots_mock = self.volume_client.volume_snapshots
|
self.snapshots_mock = self.volume_client.volume_snapshots
|
||||||
self.snapshots_mock.reset_mock()
|
self.snapshots_mock.reset_mock()
|
||||||
|
self.volumes_mock = self.volume_client.volumes
|
||||||
|
self.volumes_mock.reset_mock()
|
||||||
|
self.project_mock = self.identity_client.projects
|
||||||
|
self.project_mock.reset_mock()
|
||||||
|
|
||||||
self.volume_sdk_client.unmanage_snapshot.return_value = None
|
self.volume_sdk_client.unmanage_snapshot.return_value = None
|
||||||
|
|
||||||
|
|
||||||
|
class TestVolumeSnapshotCreate(TestVolumeSnapshot):
|
||||||
|
columns = (
|
||||||
|
'created_at',
|
||||||
|
'description',
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'properties',
|
||||||
|
'size',
|
||||||
|
'status',
|
||||||
|
'volume_id',
|
||||||
|
)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.volume = volume_fakes.create_one_volume()
|
||||||
|
self.new_snapshot = volume_fakes.create_one_snapshot(
|
||||||
|
attrs={'volume_id': self.volume.id}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.data = (
|
||||||
|
self.new_snapshot.created_at,
|
||||||
|
self.new_snapshot.description,
|
||||||
|
self.new_snapshot.id,
|
||||||
|
self.new_snapshot.name,
|
||||||
|
format_columns.DictColumn(self.new_snapshot.metadata),
|
||||||
|
self.new_snapshot.size,
|
||||||
|
self.new_snapshot.status,
|
||||||
|
self.new_snapshot.volume_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.volumes_mock.get.return_value = self.volume
|
||||||
|
self.snapshots_mock.create.return_value = self.new_snapshot
|
||||||
|
self.snapshots_mock.manage.return_value = self.new_snapshot
|
||||||
|
# Get the command object to test
|
||||||
|
self.cmd = volume_snapshot.CreateVolumeSnapshot(self.app, None)
|
||||||
|
|
||||||
|
def test_snapshot_create(self):
|
||||||
|
arglist = [
|
||||||
|
"--volume",
|
||||||
|
self.new_snapshot.volume_id,
|
||||||
|
"--description",
|
||||||
|
self.new_snapshot.description,
|
||||||
|
"--force",
|
||||||
|
'--property',
|
||||||
|
'Alpha=a',
|
||||||
|
'--property',
|
||||||
|
'Beta=b',
|
||||||
|
self.new_snapshot.name,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("volume", self.new_snapshot.volume_id),
|
||||||
|
("description", self.new_snapshot.description),
|
||||||
|
("force", True),
|
||||||
|
('property', {'Alpha': 'a', 'Beta': 'b'}),
|
||||||
|
("snapshot_name", self.new_snapshot.name),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.snapshots_mock.create.assert_called_with(
|
||||||
|
self.new_snapshot.volume_id,
|
||||||
|
force=True,
|
||||||
|
name=self.new_snapshot.name,
|
||||||
|
description=self.new_snapshot.description,
|
||||||
|
metadata={'Alpha': 'a', 'Beta': 'b'},
|
||||||
|
)
|
||||||
|
self.assertEqual(self.columns, columns)
|
||||||
|
self.assertEqual(self.data, data)
|
||||||
|
|
||||||
|
def test_snapshot_create_without_name(self):
|
||||||
|
arglist = [
|
||||||
|
"--volume",
|
||||||
|
self.new_snapshot.volume_id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("volume", self.new_snapshot.volume_id),
|
||||||
|
]
|
||||||
|
self.assertRaises(
|
||||||
|
test_utils.ParserException,
|
||||||
|
self.check_parser,
|
||||||
|
self.cmd,
|
||||||
|
arglist,
|
||||||
|
verifylist,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_snapshot_create_without_volume(self):
|
||||||
|
arglist = [
|
||||||
|
"--description",
|
||||||
|
self.new_snapshot.description,
|
||||||
|
"--force",
|
||||||
|
self.new_snapshot.name,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("description", self.new_snapshot.description),
|
||||||
|
("force", True),
|
||||||
|
("snapshot_name", self.new_snapshot.name),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.volumes_mock.get.assert_called_once_with(self.new_snapshot.name)
|
||||||
|
self.snapshots_mock.create.assert_called_once_with(
|
||||||
|
self.new_snapshot.volume_id,
|
||||||
|
force=True,
|
||||||
|
name=self.new_snapshot.name,
|
||||||
|
description=self.new_snapshot.description,
|
||||||
|
metadata=None,
|
||||||
|
)
|
||||||
|
self.assertEqual(self.columns, columns)
|
||||||
|
self.assertEqual(self.data, data)
|
||||||
|
|
||||||
|
def test_snapshot_create_with_remote_source(self):
|
||||||
|
arglist = [
|
||||||
|
'--remote-source',
|
||||||
|
'source-name=test_source_name',
|
||||||
|
'--remote-source',
|
||||||
|
'source-id=test_source_id',
|
||||||
|
'--volume',
|
||||||
|
self.new_snapshot.volume_id,
|
||||||
|
self.new_snapshot.name,
|
||||||
|
]
|
||||||
|
ref_dict = {
|
||||||
|
'source-name': 'test_source_name',
|
||||||
|
'source-id': 'test_source_id',
|
||||||
|
}
|
||||||
|
verifylist = [
|
||||||
|
('remote_source', ref_dict),
|
||||||
|
('volume', self.new_snapshot.volume_id),
|
||||||
|
("snapshot_name", self.new_snapshot.name),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.snapshots_mock.manage.assert_called_with(
|
||||||
|
volume_id=self.new_snapshot.volume_id,
|
||||||
|
ref=ref_dict,
|
||||||
|
name=self.new_snapshot.name,
|
||||||
|
description=None,
|
||||||
|
metadata=None,
|
||||||
|
)
|
||||||
|
self.snapshots_mock.create.assert_not_called()
|
||||||
|
self.assertEqual(self.columns, columns)
|
||||||
|
self.assertEqual(self.data, data)
|
||||||
|
|
||||||
|
|
||||||
class TestVolumeSnapshotDelete(TestVolumeSnapshot):
|
class TestVolumeSnapshotDelete(TestVolumeSnapshot):
|
||||||
snapshots = volume_fakes.create_snapshots(count=2)
|
snapshots = volume_fakes.create_snapshots(count=2)
|
||||||
|
|
||||||
@ -158,3 +314,476 @@ class TestVolumeSnapshotDelete(TestVolumeSnapshot):
|
|||||||
calls.append(mock.call(s.id))
|
calls.append(mock.call(s.id))
|
||||||
self.volume_sdk_client.unmanage_snapshot.assert_has_calls(calls)
|
self.volume_sdk_client.unmanage_snapshot.assert_has_calls(calls)
|
||||||
self.assertIsNone(result)
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
|
||||||
|
class TestVolumeSnapshotList(TestVolumeSnapshot):
|
||||||
|
volume = volume_fakes.create_one_volume()
|
||||||
|
project = project_fakes.FakeProject.create_one_project()
|
||||||
|
snapshots = volume_fakes.create_snapshots(
|
||||||
|
attrs={'volume_id': volume.name}, count=3
|
||||||
|
)
|
||||||
|
|
||||||
|
columns = ["ID", "Name", "Description", "Status", "Size"]
|
||||||
|
columns_long = columns + ["Created At", "Volume", "Properties"]
|
||||||
|
|
||||||
|
data = []
|
||||||
|
for s in snapshots:
|
||||||
|
data.append(
|
||||||
|
(
|
||||||
|
s.id,
|
||||||
|
s.name,
|
||||||
|
s.description,
|
||||||
|
s.status,
|
||||||
|
s.size,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
data_long = []
|
||||||
|
for s in snapshots:
|
||||||
|
data_long.append(
|
||||||
|
(
|
||||||
|
s.id,
|
||||||
|
s.name,
|
||||||
|
s.description,
|
||||||
|
s.status,
|
||||||
|
s.size,
|
||||||
|
s.created_at,
|
||||||
|
volume_snapshot.VolumeIdColumn(
|
||||||
|
s.volume_id, volume_cache={volume.id: volume}
|
||||||
|
),
|
||||||
|
format_columns.DictColumn(s.metadata),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.volumes_mock.list.return_value = [self.volume]
|
||||||
|
self.volumes_mock.get.return_value = self.volume
|
||||||
|
self.project_mock.get.return_value = self.project
|
||||||
|
self.snapshots_mock.list.return_value = self.snapshots
|
||||||
|
# Get the command to test
|
||||||
|
self.cmd = volume_snapshot.ListVolumeSnapshot(self.app, None)
|
||||||
|
|
||||||
|
def test_snapshot_list_without_options(self):
|
||||||
|
arglist = []
|
||||||
|
verifylist = [('all_projects', False), ('long', False)]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.snapshots_mock.list.assert_called_once_with(
|
||||||
|
limit=None,
|
||||||
|
marker=None,
|
||||||
|
search_opts={
|
||||||
|
'all_tenants': False,
|
||||||
|
'name': None,
|
||||||
|
'status': None,
|
||||||
|
'project_id': None,
|
||||||
|
'volume_id': None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(self.columns, columns)
|
||||||
|
self.assertEqual(self.data, list(data))
|
||||||
|
|
||||||
|
def test_snapshot_list_with_options(self):
|
||||||
|
arglist = [
|
||||||
|
"--long",
|
||||||
|
"--limit",
|
||||||
|
"2",
|
||||||
|
"--project",
|
||||||
|
self.project.id,
|
||||||
|
"--marker",
|
||||||
|
self.snapshots[0].id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("long", True),
|
||||||
|
("limit", 2),
|
||||||
|
("project", self.project.id),
|
||||||
|
("marker", self.snapshots[0].id),
|
||||||
|
('all_projects', False),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.snapshots_mock.list.assert_called_once_with(
|
||||||
|
limit=2,
|
||||||
|
marker=self.snapshots[0].id,
|
||||||
|
search_opts={
|
||||||
|
'all_tenants': True,
|
||||||
|
'project_id': self.project.id,
|
||||||
|
'name': None,
|
||||||
|
'status': None,
|
||||||
|
'volume_id': None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(self.columns_long, columns)
|
||||||
|
self.assertEqual(self.data_long, list(data))
|
||||||
|
|
||||||
|
def test_snapshot_list_all_projects(self):
|
||||||
|
arglist = [
|
||||||
|
'--all-projects',
|
||||||
|
]
|
||||||
|
verifylist = [('long', False), ('all_projects', True)]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.snapshots_mock.list.assert_called_once_with(
|
||||||
|
limit=None,
|
||||||
|
marker=None,
|
||||||
|
search_opts={
|
||||||
|
'all_tenants': True,
|
||||||
|
'name': None,
|
||||||
|
'status': None,
|
||||||
|
'project_id': None,
|
||||||
|
'volume_id': None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(self.columns, columns)
|
||||||
|
self.assertEqual(self.data, list(data))
|
||||||
|
|
||||||
|
def test_snapshot_list_name_option(self):
|
||||||
|
arglist = [
|
||||||
|
'--name',
|
||||||
|
self.snapshots[0].name,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('all_projects', False),
|
||||||
|
('long', False),
|
||||||
|
('name', self.snapshots[0].name),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.snapshots_mock.list.assert_called_once_with(
|
||||||
|
limit=None,
|
||||||
|
marker=None,
|
||||||
|
search_opts={
|
||||||
|
'all_tenants': False,
|
||||||
|
'name': self.snapshots[0].name,
|
||||||
|
'status': None,
|
||||||
|
'project_id': None,
|
||||||
|
'volume_id': None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(self.columns, columns)
|
||||||
|
self.assertEqual(self.data, list(data))
|
||||||
|
|
||||||
|
def test_snapshot_list_status_option(self):
|
||||||
|
arglist = [
|
||||||
|
'--status',
|
||||||
|
self.snapshots[0].status,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('all_projects', False),
|
||||||
|
('long', False),
|
||||||
|
('status', self.snapshots[0].status),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.snapshots_mock.list.assert_called_once_with(
|
||||||
|
limit=None,
|
||||||
|
marker=None,
|
||||||
|
search_opts={
|
||||||
|
'all_tenants': False,
|
||||||
|
'name': None,
|
||||||
|
'status': self.snapshots[0].status,
|
||||||
|
'project_id': None,
|
||||||
|
'volume_id': None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(self.columns, columns)
|
||||||
|
self.assertEqual(self.data, list(data))
|
||||||
|
|
||||||
|
def test_snapshot_list_volumeid_option(self):
|
||||||
|
arglist = [
|
||||||
|
'--volume',
|
||||||
|
self.volume.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('all_projects', False),
|
||||||
|
('long', False),
|
||||||
|
('volume', self.volume.id),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.snapshots_mock.list.assert_called_once_with(
|
||||||
|
limit=None,
|
||||||
|
marker=None,
|
||||||
|
search_opts={
|
||||||
|
'all_tenants': False,
|
||||||
|
'name': None,
|
||||||
|
'status': None,
|
||||||
|
'project_id': None,
|
||||||
|
'volume_id': self.volume.id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(self.columns, columns)
|
||||||
|
self.assertEqual(self.data, list(data))
|
||||||
|
|
||||||
|
def test_snapshot_list_negative_limit(self):
|
||||||
|
arglist = [
|
||||||
|
"--limit",
|
||||||
|
"-2",
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("limit", -2),
|
||||||
|
]
|
||||||
|
self.assertRaises(
|
||||||
|
test_utils.ParserException,
|
||||||
|
self.check_parser,
|
||||||
|
self.cmd,
|
||||||
|
arglist,
|
||||||
|
verifylist,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestVolumeSnapshotSet(TestVolumeSnapshot):
|
||||||
|
snapshot = volume_fakes.create_one_snapshot()
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.snapshots_mock.get.return_value = self.snapshot
|
||||||
|
self.snapshots_mock.set_metadata.return_value = None
|
||||||
|
self.snapshots_mock.update.return_value = None
|
||||||
|
# Get the command object to mock
|
||||||
|
self.cmd = volume_snapshot.SetVolumeSnapshot(self.app, None)
|
||||||
|
|
||||||
|
def test_snapshot_set_no_option(self):
|
||||||
|
arglist = [
|
||||||
|
self.snapshot.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("snapshot", self.snapshot.id),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
self.snapshots_mock.get.assert_called_once_with(parsed_args.snapshot)
|
||||||
|
self.assertNotCalled(self.snapshots_mock.reset_state)
|
||||||
|
self.assertNotCalled(self.snapshots_mock.update)
|
||||||
|
self.assertNotCalled(self.snapshots_mock.set_metadata)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_snapshot_set_name_and_property(self):
|
||||||
|
arglist = [
|
||||||
|
"--name",
|
||||||
|
"new_snapshot",
|
||||||
|
"--property",
|
||||||
|
"x=y",
|
||||||
|
"--property",
|
||||||
|
"foo=foo",
|
||||||
|
self.snapshot.id,
|
||||||
|
]
|
||||||
|
new_property = {"x": "y", "foo": "foo"}
|
||||||
|
verifylist = [
|
||||||
|
("name", "new_snapshot"),
|
||||||
|
("property", new_property),
|
||||||
|
("snapshot", self.snapshot.id),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
kwargs = {
|
||||||
|
"name": "new_snapshot",
|
||||||
|
}
|
||||||
|
self.snapshots_mock.update.assert_called_with(
|
||||||
|
self.snapshot.id, **kwargs
|
||||||
|
)
|
||||||
|
self.snapshots_mock.set_metadata.assert_called_with(
|
||||||
|
self.snapshot.id, new_property
|
||||||
|
)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_snapshot_set_with_no_property(self):
|
||||||
|
arglist = [
|
||||||
|
"--no-property",
|
||||||
|
self.snapshot.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("no_property", True),
|
||||||
|
("snapshot", self.snapshot.id),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
self.snapshots_mock.get.assert_called_once_with(parsed_args.snapshot)
|
||||||
|
self.assertNotCalled(self.snapshots_mock.reset_state)
|
||||||
|
self.assertNotCalled(self.snapshots_mock.update)
|
||||||
|
self.assertNotCalled(self.snapshots_mock.set_metadata)
|
||||||
|
self.snapshots_mock.delete_metadata.assert_called_with(
|
||||||
|
self.snapshot.id, ["foo"]
|
||||||
|
)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_snapshot_set_with_no_property_and_property(self):
|
||||||
|
arglist = [
|
||||||
|
"--no-property",
|
||||||
|
"--property",
|
||||||
|
"foo_1=bar_1",
|
||||||
|
self.snapshot.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("no_property", True),
|
||||||
|
("property", {"foo_1": "bar_1"}),
|
||||||
|
("snapshot", self.snapshot.id),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
self.snapshots_mock.get.assert_called_once_with(parsed_args.snapshot)
|
||||||
|
self.assertNotCalled(self.snapshots_mock.reset_state)
|
||||||
|
self.assertNotCalled(self.snapshots_mock.update)
|
||||||
|
self.snapshots_mock.delete_metadata.assert_called_with(
|
||||||
|
self.snapshot.id, ["foo"]
|
||||||
|
)
|
||||||
|
self.snapshots_mock.set_metadata.assert_called_once_with(
|
||||||
|
self.snapshot.id, {"foo_1": "bar_1"}
|
||||||
|
)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_snapshot_set_state_to_error(self):
|
||||||
|
arglist = ["--state", "error", self.snapshot.id]
|
||||||
|
verifylist = [("state", "error"), ("snapshot", self.snapshot.id)]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.snapshots_mock.reset_state.assert_called_with(
|
||||||
|
self.snapshot.id, "error"
|
||||||
|
)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_volume_set_state_failed(self):
|
||||||
|
self.snapshots_mock.reset_state.side_effect = exceptions.CommandError()
|
||||||
|
arglist = ['--state', 'error', self.snapshot.id]
|
||||||
|
verifylist = [('state', 'error'), ('snapshot', self.snapshot.id)]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
try:
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
self.fail('CommandError should be raised.')
|
||||||
|
except exceptions.CommandError as e:
|
||||||
|
self.assertEqual(
|
||||||
|
'One or more of the set operations failed', str(e)
|
||||||
|
)
|
||||||
|
self.snapshots_mock.reset_state.assert_called_once_with(
|
||||||
|
self.snapshot.id, 'error'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_volume_set_name_and_state_failed(self):
|
||||||
|
self.snapshots_mock.reset_state.side_effect = exceptions.CommandError()
|
||||||
|
arglist = [
|
||||||
|
'--state',
|
||||||
|
'error',
|
||||||
|
"--name",
|
||||||
|
"new_snapshot",
|
||||||
|
self.snapshot.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('state', 'error'),
|
||||||
|
("name", "new_snapshot"),
|
||||||
|
('snapshot', self.snapshot.id),
|
||||||
|
]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
try:
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
self.fail('CommandError should be raised.')
|
||||||
|
except exceptions.CommandError as e:
|
||||||
|
self.assertEqual(
|
||||||
|
'One or more of the set operations failed', str(e)
|
||||||
|
)
|
||||||
|
kwargs = {
|
||||||
|
"name": "new_snapshot",
|
||||||
|
}
|
||||||
|
self.snapshots_mock.update.assert_called_once_with(
|
||||||
|
self.snapshot.id, **kwargs
|
||||||
|
)
|
||||||
|
self.snapshots_mock.reset_state.assert_called_once_with(
|
||||||
|
self.snapshot.id, 'error'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestVolumeSnapshotShow(TestVolumeSnapshot):
|
||||||
|
columns = (
|
||||||
|
'created_at',
|
||||||
|
'description',
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'properties',
|
||||||
|
'size',
|
||||||
|
'status',
|
||||||
|
'volume_id',
|
||||||
|
)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.snapshot = volume_fakes.create_one_snapshot()
|
||||||
|
|
||||||
|
self.data = (
|
||||||
|
self.snapshot.created_at,
|
||||||
|
self.snapshot.description,
|
||||||
|
self.snapshot.id,
|
||||||
|
self.snapshot.name,
|
||||||
|
format_columns.DictColumn(self.snapshot.metadata),
|
||||||
|
self.snapshot.size,
|
||||||
|
self.snapshot.status,
|
||||||
|
self.snapshot.volume_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.snapshots_mock.get.return_value = self.snapshot
|
||||||
|
# Get the command object to test
|
||||||
|
self.cmd = volume_snapshot.ShowVolumeSnapshot(self.app, None)
|
||||||
|
|
||||||
|
def test_snapshot_show(self):
|
||||||
|
arglist = [self.snapshot.id]
|
||||||
|
verifylist = [("snapshot", self.snapshot.id)]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
self.snapshots_mock.get.assert_called_with(self.snapshot.id)
|
||||||
|
|
||||||
|
self.assertEqual(self.columns, columns)
|
||||||
|
self.assertCountEqual(self.data, data)
|
||||||
|
|
||||||
|
|
||||||
|
class TestVolumeSnapshotUnset(TestVolumeSnapshot):
|
||||||
|
snapshot = volume_fakes.create_one_snapshot()
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.snapshots_mock.get.return_value = self.snapshot
|
||||||
|
self.snapshots_mock.delete_metadata.return_value = None
|
||||||
|
# Get the command object to mock
|
||||||
|
self.cmd = volume_snapshot.UnsetVolumeSnapshot(self.app, None)
|
||||||
|
|
||||||
|
def test_snapshot_unset(self):
|
||||||
|
arglist = [
|
||||||
|
"--property",
|
||||||
|
"foo",
|
||||||
|
self.snapshot.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("property", ["foo"]),
|
||||||
|
("snapshot", self.snapshot.id),
|
||||||
|
]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.snapshots_mock.delete_metadata.assert_called_with(
|
||||||
|
self.snapshot.id, ["foo"]
|
||||||
|
)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
@ -14,17 +14,144 @@
|
|||||||
|
|
||||||
"""Volume v3 snapshot action implementations"""
|
"""Volume v3 snapshot action implementations"""
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import functools
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from cliff import columns as cliff_columns
|
||||||
|
from osc_lib.cli import format_columns
|
||||||
|
from osc_lib.cli import parseractions
|
||||||
from osc_lib.command import command
|
from osc_lib.command import command
|
||||||
from osc_lib import exceptions
|
from osc_lib import exceptions
|
||||||
from osc_lib import utils
|
from osc_lib import utils
|
||||||
|
|
||||||
|
from openstackclient.common import pagination
|
||||||
from openstackclient.i18n import _
|
from openstackclient.i18n import _
|
||||||
|
from openstackclient.identity import common as identity_common
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class VolumeIdColumn(cliff_columns.FormattableColumn):
|
||||||
|
"""Formattable column for volume ID column.
|
||||||
|
|
||||||
|
Unlike the parent FormattableColumn class, the initializer of the
|
||||||
|
class takes volume_cache as the second argument.
|
||||||
|
osc_lib.utils.get_item_properties instantiate cliff FormattableColumn
|
||||||
|
object with a single parameter "column value", so you need to pass
|
||||||
|
a partially initialized class like
|
||||||
|
``functools.partial(VolumeIdColumn, volume_cache)``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, value, volume_cache=None):
|
||||||
|
super().__init__(value)
|
||||||
|
self._volume_cache = volume_cache or {}
|
||||||
|
|
||||||
|
def human_readable(self):
|
||||||
|
"""Return a volume name if available
|
||||||
|
|
||||||
|
:rtype: either the volume ID or name
|
||||||
|
"""
|
||||||
|
volume_id = self._value
|
||||||
|
volume = volume_id
|
||||||
|
if volume_id in self._volume_cache.keys():
|
||||||
|
volume = self._volume_cache[volume_id].name
|
||||||
|
return volume
|
||||||
|
|
||||||
|
|
||||||
|
class CreateVolumeSnapshot(command.ShowOne):
|
||||||
|
_description = _("Create new volume snapshot")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super().get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
"snapshot_name",
|
||||||
|
metavar="<snapshot-name>",
|
||||||
|
help=_("Name of the new snapshot"),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--volume",
|
||||||
|
metavar="<volume>",
|
||||||
|
help=_(
|
||||||
|
"Volume to snapshot (name or ID) (default is <snapshot-name>)"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--description",
|
||||||
|
metavar="<description>",
|
||||||
|
help=_("Description of the snapshot"),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--force",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help=_(
|
||||||
|
"Create a snapshot attached to an instance. Default is False"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--property",
|
||||||
|
metavar="<key=value>",
|
||||||
|
action=parseractions.KeyValueAction,
|
||||||
|
help=_(
|
||||||
|
"Set a property to this snapshot "
|
||||||
|
"(repeat option to set multiple properties)"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--remote-source",
|
||||||
|
metavar="<key=value>",
|
||||||
|
action=parseractions.KeyValueAction,
|
||||||
|
help=_(
|
||||||
|
"The attribute(s) of the existing remote volume snapshot "
|
||||||
|
"(admin required) (repeat option to specify multiple "
|
||||||
|
"attributes) e.g.: '--remote-source source-name=test_name "
|
||||||
|
"--remote-source source-id=test_id'"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
volume_client = self.app.client_manager.volume
|
||||||
|
volume = parsed_args.volume
|
||||||
|
if not parsed_args.volume:
|
||||||
|
volume = parsed_args.snapshot_name
|
||||||
|
volume_id = utils.find_resource(volume_client.volumes, volume).id
|
||||||
|
if parsed_args.remote_source:
|
||||||
|
# Create a new snapshot from an existing remote snapshot source
|
||||||
|
if parsed_args.force:
|
||||||
|
msg = _(
|
||||||
|
"'--force' option will not work when you create "
|
||||||
|
"new volume snapshot from an existing remote "
|
||||||
|
"volume snapshot"
|
||||||
|
)
|
||||||
|
LOG.warning(msg)
|
||||||
|
snapshot = volume_client.volume_snapshots.manage(
|
||||||
|
volume_id=volume_id,
|
||||||
|
ref=parsed_args.remote_source,
|
||||||
|
name=parsed_args.snapshot_name,
|
||||||
|
description=parsed_args.description,
|
||||||
|
metadata=parsed_args.property,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# create a new snapshot from scratch
|
||||||
|
snapshot = volume_client.volume_snapshots.create(
|
||||||
|
volume_id,
|
||||||
|
force=parsed_args.force,
|
||||||
|
name=parsed_args.snapshot_name,
|
||||||
|
description=parsed_args.description,
|
||||||
|
metadata=parsed_args.property,
|
||||||
|
)
|
||||||
|
snapshot._info.update(
|
||||||
|
{
|
||||||
|
'properties': format_columns.DictColumn(
|
||||||
|
snapshot._info.pop('metadata')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return zip(*sorted(snapshot._info.items()))
|
||||||
|
|
||||||
|
|
||||||
class DeleteVolumeSnapshot(command.Command):
|
class DeleteVolumeSnapshot(command.Command):
|
||||||
_description = _("Delete volume snapshot(s)")
|
_description = _("Delete volume snapshot(s)")
|
||||||
|
|
||||||
@ -96,3 +223,316 @@ class DeleteVolumeSnapshot(command.Command):
|
|||||||
'total': total,
|
'total': total,
|
||||||
}
|
}
|
||||||
raise exceptions.CommandError(msg)
|
raise exceptions.CommandError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
class ListVolumeSnapshot(command.Lister):
|
||||||
|
_description = _("List volume snapshots")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super().get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'--all-projects',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help=_('Include all projects (admin only)'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--project',
|
||||||
|
metavar='<project>',
|
||||||
|
help=_('Filter results by project (name or ID) (admin only)'),
|
||||||
|
)
|
||||||
|
identity_common.add_project_domain_option_to_parser(parser)
|
||||||
|
parser.add_argument(
|
||||||
|
'--long',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help=_('List additional fields in output'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--name',
|
||||||
|
metavar='<name>',
|
||||||
|
default=None,
|
||||||
|
help=_('Filters results by a name.'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--status',
|
||||||
|
metavar='<status>',
|
||||||
|
choices=[
|
||||||
|
'available',
|
||||||
|
'error',
|
||||||
|
'creating',
|
||||||
|
'deleting',
|
||||||
|
'error_deleting',
|
||||||
|
],
|
||||||
|
help=_(
|
||||||
|
"Filters results by a status. "
|
||||||
|
"('available', 'error', 'creating', 'deleting'"
|
||||||
|
" or 'error_deleting')"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--volume',
|
||||||
|
metavar='<volume>',
|
||||||
|
default=None,
|
||||||
|
help=_('Filters results by a volume (name or ID).'),
|
||||||
|
)
|
||||||
|
pagination.add_marker_pagination_option_to_parser(parser)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
volume_client = self.app.client_manager.volume
|
||||||
|
identity_client = self.app.client_manager.identity
|
||||||
|
|
||||||
|
if parsed_args.long:
|
||||||
|
columns = [
|
||||||
|
'ID',
|
||||||
|
'Name',
|
||||||
|
'Description',
|
||||||
|
'Status',
|
||||||
|
'Size',
|
||||||
|
'Created At',
|
||||||
|
'Volume ID',
|
||||||
|
'Metadata',
|
||||||
|
]
|
||||||
|
column_headers = copy.deepcopy(columns)
|
||||||
|
column_headers[6] = 'Volume'
|
||||||
|
column_headers[7] = 'Properties'
|
||||||
|
else:
|
||||||
|
columns = ['ID', 'Name', 'Description', 'Status', 'Size']
|
||||||
|
column_headers = copy.deepcopy(columns)
|
||||||
|
|
||||||
|
# Cache the volume list
|
||||||
|
volume_cache = {}
|
||||||
|
try:
|
||||||
|
for s in volume_client.volumes.list():
|
||||||
|
volume_cache[s.id] = s
|
||||||
|
except Exception: # noqa: S110
|
||||||
|
# Just forget it if there's any trouble
|
||||||
|
pass
|
||||||
|
_VolumeIdColumn = functools.partial(
|
||||||
|
VolumeIdColumn, volume_cache=volume_cache
|
||||||
|
)
|
||||||
|
|
||||||
|
volume_id = None
|
||||||
|
if parsed_args.volume:
|
||||||
|
volume_id = utils.find_resource(
|
||||||
|
volume_client.volumes, parsed_args.volume
|
||||||
|
).id
|
||||||
|
|
||||||
|
project_id = None
|
||||||
|
if parsed_args.project:
|
||||||
|
project_id = identity_common.find_project(
|
||||||
|
identity_client,
|
||||||
|
parsed_args.project,
|
||||||
|
parsed_args.project_domain,
|
||||||
|
).id
|
||||||
|
|
||||||
|
# set value of 'all_tenants' when using project option
|
||||||
|
all_projects = (
|
||||||
|
True if parsed_args.project else parsed_args.all_projects
|
||||||
|
)
|
||||||
|
|
||||||
|
search_opts = {
|
||||||
|
'all_tenants': all_projects,
|
||||||
|
'project_id': project_id,
|
||||||
|
'name': parsed_args.name,
|
||||||
|
'status': parsed_args.status,
|
||||||
|
'volume_id': volume_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
data = volume_client.volume_snapshots.list(
|
||||||
|
search_opts=search_opts,
|
||||||
|
marker=parsed_args.marker,
|
||||||
|
limit=parsed_args.limit,
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
column_headers,
|
||||||
|
(
|
||||||
|
utils.get_item_properties(
|
||||||
|
s,
|
||||||
|
columns,
|
||||||
|
formatters={
|
||||||
|
'Metadata': format_columns.DictColumn,
|
||||||
|
'Volume ID': _VolumeIdColumn,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
for s in data
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SetVolumeSnapshot(command.Command):
|
||||||
|
_description = _("Set volume snapshot properties")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super().get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'snapshot',
|
||||||
|
metavar='<snapshot>',
|
||||||
|
help=_('Snapshot to modify (name or ID)'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--name', metavar='<name>', help=_('New snapshot name')
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--description',
|
||||||
|
metavar='<description>',
|
||||||
|
help=_('New snapshot description'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-property",
|
||||||
|
dest="no_property",
|
||||||
|
action="store_true",
|
||||||
|
help=_(
|
||||||
|
"Remove all properties from <snapshot> "
|
||||||
|
"(specify both --no-property and --property to "
|
||||||
|
"remove the current properties before setting "
|
||||||
|
"new properties.)"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--property',
|
||||||
|
metavar='<key=value>',
|
||||||
|
action=parseractions.KeyValueAction,
|
||||||
|
help=_(
|
||||||
|
'Property to add/change for this snapshot '
|
||||||
|
'(repeat option to set multiple properties)'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--state',
|
||||||
|
metavar='<state>',
|
||||||
|
choices=[
|
||||||
|
'available',
|
||||||
|
'error',
|
||||||
|
'creating',
|
||||||
|
'deleting',
|
||||||
|
'error_deleting',
|
||||||
|
],
|
||||||
|
help=_(
|
||||||
|
'New snapshot state. ("available", "error", "creating", '
|
||||||
|
'"deleting", or "error_deleting") (admin only) '
|
||||||
|
'(This option simply changes the state of the snapshot '
|
||||||
|
'in the database with no regard to actual status, '
|
||||||
|
'exercise caution when using)'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
volume_client = self.app.client_manager.volume
|
||||||
|
snapshot = utils.find_resource(
|
||||||
|
volume_client.volume_snapshots, parsed_args.snapshot
|
||||||
|
)
|
||||||
|
|
||||||
|
result = 0
|
||||||
|
if parsed_args.no_property:
|
||||||
|
try:
|
||||||
|
key_list = snapshot.metadata.keys()
|
||||||
|
volume_client.volume_snapshots.delete_metadata(
|
||||||
|
snapshot.id,
|
||||||
|
list(key_list),
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(_("Failed to clean snapshot properties: %s"), e)
|
||||||
|
result += 1
|
||||||
|
|
||||||
|
if parsed_args.property:
|
||||||
|
try:
|
||||||
|
volume_client.volume_snapshots.set_metadata(
|
||||||
|
snapshot.id, parsed_args.property
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(_("Failed to set snapshot property: %s"), e)
|
||||||
|
result += 1
|
||||||
|
|
||||||
|
if parsed_args.state:
|
||||||
|
try:
|
||||||
|
volume_client.volume_snapshots.reset_state(
|
||||||
|
snapshot.id, parsed_args.state
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(_("Failed to set snapshot state: %s"), e)
|
||||||
|
result += 1
|
||||||
|
|
||||||
|
kwargs = {}
|
||||||
|
if parsed_args.name:
|
||||||
|
kwargs['name'] = parsed_args.name
|
||||||
|
if parsed_args.description:
|
||||||
|
kwargs['description'] = parsed_args.description
|
||||||
|
if kwargs:
|
||||||
|
try:
|
||||||
|
volume_client.volume_snapshots.update(snapshot.id, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(
|
||||||
|
_("Failed to update snapshot name or description: %s"),
|
||||||
|
e,
|
||||||
|
)
|
||||||
|
result += 1
|
||||||
|
|
||||||
|
if result > 0:
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
_("One or more of the set operations failed")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ShowVolumeSnapshot(command.ShowOne):
|
||||||
|
_description = _("Display volume snapshot details")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super().get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
"snapshot",
|
||||||
|
metavar="<snapshot>",
|
||||||
|
help=_("Snapshot to display (name or ID)"),
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
volume_client = self.app.client_manager.volume
|
||||||
|
snapshot = utils.find_resource(
|
||||||
|
volume_client.volume_snapshots, parsed_args.snapshot
|
||||||
|
)
|
||||||
|
snapshot._info.update(
|
||||||
|
{
|
||||||
|
'properties': format_columns.DictColumn(
|
||||||
|
snapshot._info.pop('metadata')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return zip(*sorted(snapshot._info.items()))
|
||||||
|
|
||||||
|
|
||||||
|
class UnsetVolumeSnapshot(command.Command):
|
||||||
|
_description = _("Unset volume snapshot properties")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super().get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'snapshot',
|
||||||
|
metavar='<snapshot>',
|
||||||
|
help=_('Snapshot to modify (name or ID)'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--property',
|
||||||
|
metavar='<key>',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
help=_(
|
||||||
|
'Property to remove from snapshot '
|
||||||
|
'(repeat option to remove multiple properties)'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
volume_client = self.app.client_manager.volume
|
||||||
|
snapshot = utils.find_resource(
|
||||||
|
volume_client.volume_snapshots, parsed_args.snapshot
|
||||||
|
)
|
||||||
|
|
||||||
|
if parsed_args.property:
|
||||||
|
volume_client.volume_snapshots.delete_metadata(
|
||||||
|
snapshot.id,
|
||||||
|
parsed_args.property,
|
||||||
|
)
|
||||||
|
10
setup.cfg
10
setup.cfg
@ -780,12 +780,12 @@ openstack.volume.v3 =
|
|||||||
block_storage_resource_filter_list = openstackclient.volume.v3.block_storage_resource_filter:ListBlockStorageResourceFilter
|
block_storage_resource_filter_list = openstackclient.volume.v3.block_storage_resource_filter:ListBlockStorageResourceFilter
|
||||||
block_storage_resource_filter_show = openstackclient.volume.v3.block_storage_resource_filter:ShowBlockStorageResourceFilter
|
block_storage_resource_filter_show = openstackclient.volume.v3.block_storage_resource_filter:ShowBlockStorageResourceFilter
|
||||||
|
|
||||||
volume_snapshot_create = openstackclient.volume.v2.volume_snapshot:CreateVolumeSnapshot
|
volume_snapshot_create = openstackclient.volume.v3.volume_snapshot:CreateVolumeSnapshot
|
||||||
volume_snapshot_delete = openstackclient.volume.v3.volume_snapshot:DeleteVolumeSnapshot
|
volume_snapshot_delete = openstackclient.volume.v3.volume_snapshot:DeleteVolumeSnapshot
|
||||||
volume_snapshot_list = openstackclient.volume.v2.volume_snapshot:ListVolumeSnapshot
|
volume_snapshot_list = openstackclient.volume.v3.volume_snapshot:ListVolumeSnapshot
|
||||||
volume_snapshot_set = openstackclient.volume.v2.volume_snapshot:SetVolumeSnapshot
|
volume_snapshot_set = openstackclient.volume.v3.volume_snapshot:SetVolumeSnapshot
|
||||||
volume_snapshot_show = openstackclient.volume.v2.volume_snapshot:ShowVolumeSnapshot
|
volume_snapshot_show = openstackclient.volume.v3.volume_snapshot:ShowVolumeSnapshot
|
||||||
volume_snapshot_unset = openstackclient.volume.v2.volume_snapshot:UnsetVolumeSnapshot
|
volume_snapshot_unset = openstackclient.volume.v3.volume_snapshot:UnsetVolumeSnapshot
|
||||||
|
|
||||||
volume_type_create = openstackclient.volume.v3.volume_type:CreateVolumeType
|
volume_type_create = openstackclient.volume.v3.volume_type:CreateVolumeType
|
||||||
volume_type_delete = openstackclient.volume.v3.volume_type:DeleteVolumeType
|
volume_type_delete = openstackclient.volume.v3.volume_type:DeleteVolumeType
|
||||||
|
Reference in New Issue
Block a user