volume: Remove Cinder v1 support
The Cinder v1 API was removed in Queens [1]. Its replacement, the v2 API, has existed since Grizzly [2]. More importantly, the v1 commands are implemented using python-cinderclient but support for the v1 API was removed from python-cinderclient in Train [3], meaning none of these have worked since then. Clearly if no one has noticed or cared in the 6 years or so since that happened, it's safe to say we can delete these commands. [1]3e91de956e
[2]75ca60f619
[3]2189e5702b
Change-Id: Ibe1cd6461d2cb78826467078aa17272f171746aa Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
parent
3eb063d4f7
commit
e6be9a3edf
@ -3,7 +3,7 @@ image
|
||||
=====
|
||||
|
||||
.. NOTE(efried): This page is hidden from the main TOC; it's here so links in
|
||||
the wild redirect somewhere sane, because previously identity v2 and v3 were
|
||||
the wild redirect somewhere sane, because previously image v2 and v3 were
|
||||
combined in a single page.
|
||||
|
||||
.. toctree::
|
||||
|
@ -4,7 +4,7 @@ limits
|
||||
|
||||
The Compute and Block Storage APIs have resource usage limits.
|
||||
|
||||
Compute v2, Block Storage v1
|
||||
Block Storage v2, v3; Compute v2
|
||||
|
||||
|
||||
.. autoprogram-cliff:: openstack.common
|
||||
|
@ -5,7 +5,7 @@ quota
|
||||
Resource quotas appear in multiple APIs, OpenStackClient presents them as a
|
||||
single object with multiple properties.
|
||||
|
||||
Block Storage v1, v2, Compute v2, Network v2
|
||||
Block Storage v1, v3; Compute v2; Network v2
|
||||
|
||||
.. autoprogram-cliff:: openstack.common
|
||||
:command: quota *
|
||||
|
@ -2,7 +2,7 @@
|
||||
volume backup
|
||||
=============
|
||||
|
||||
Block Storage v1, v2, v3
|
||||
Block Storage v2, v3
|
||||
|
||||
.. autoprogram-cliff:: openstack.volume.v3
|
||||
:command: volume backup *
|
||||
|
@ -2,7 +2,7 @@
|
||||
volume qos
|
||||
==========
|
||||
|
||||
Block Storage v1, v2, v3
|
||||
Block Storage v2, v3
|
||||
|
||||
.. autoprogram-cliff:: openstack.volume.v3
|
||||
:command: volume qos *
|
||||
|
@ -2,7 +2,7 @@
|
||||
volume service
|
||||
==============
|
||||
|
||||
Block Storage v1, v2, v3
|
||||
Block Storage v2, v3
|
||||
|
||||
.. autoprogram-cliff:: openstack.volume.v3
|
||||
:command: volume service *
|
||||
|
@ -2,7 +2,7 @@
|
||||
volume snapshot
|
||||
===============
|
||||
|
||||
Block Storage v1, v2, v3
|
||||
Block Storage v2, v3
|
||||
|
||||
.. autoprogram-cliff:: openstack.volume.v3
|
||||
:command: volume snapshot *
|
||||
|
@ -2,7 +2,7 @@
|
||||
volume transfer request
|
||||
=======================
|
||||
|
||||
Block Storage v1, v2, v3
|
||||
Block Storage v2, v3
|
||||
|
||||
.. autoprogram-cliff:: openstack.volume.v3
|
||||
:command: volume transfer request *
|
||||
|
@ -2,7 +2,7 @@
|
||||
volume type
|
||||
===========
|
||||
|
||||
Block Storage v1, v2, v3
|
||||
Block Storage v2, v3
|
||||
|
||||
.. autoprogram-cliff:: openstack.volume.v3
|
||||
:command: volume type *
|
||||
|
@ -2,7 +2,7 @@
|
||||
volume
|
||||
======
|
||||
|
||||
Block Storage v1, v2
|
||||
Block Storage v2, v3
|
||||
|
||||
.. autoprogram-cliff:: openstack.volume.v3
|
||||
:command: volume create
|
||||
|
@ -1,35 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import fixtures
|
||||
|
||||
from openstackclient.tests.functional.volume import base as volume_base
|
||||
|
||||
|
||||
class BaseVolumeTests(volume_base.BaseVolumeTests):
|
||||
"""Base class for Volume functional tests"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.haz_volume_v1 = cls.is_service_enabled('block-storage', '1.0')
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
if not self.haz_volume_v1:
|
||||
self.skipTest("No Volume v1 service present")
|
||||
|
||||
ver_fixture = fixtures.EnvironmentVariable(
|
||||
'OS_VOLUME_API_VERSION', '1'
|
||||
)
|
||||
self.useFixture(ver_fixture)
|
@ -1,100 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import uuid
|
||||
|
||||
from openstackclient.tests.functional.volume.v1 import common
|
||||
|
||||
|
||||
class QosTests(common.BaseVolumeTests):
|
||||
"""Functional tests for volume qos."""
|
||||
|
||||
def test_volume_qos_create_list(self):
|
||||
"""Test create, list, delete multiple"""
|
||||
name1 = uuid.uuid4().hex
|
||||
cmd_output = self.openstack(
|
||||
'volume qos create ' + name1,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual(name1, cmd_output['name'])
|
||||
|
||||
name2 = uuid.uuid4().hex
|
||||
cmd_output = self.openstack(
|
||||
'volume qos create ' + name2,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual(name2, cmd_output['name'])
|
||||
|
||||
# Test list
|
||||
cmd_output = self.openstack(
|
||||
'volume qos list',
|
||||
parse_output=True,
|
||||
)
|
||||
names = [x["Name"] for x in cmd_output]
|
||||
self.assertIn(name1, names)
|
||||
self.assertIn(name2, names)
|
||||
|
||||
# Test delete multiple
|
||||
del_output = self.openstack('volume qos delete ' + name1 + ' ' + name2)
|
||||
self.assertOutput('', del_output)
|
||||
|
||||
def test_volume_qos_set_show_unset(self):
|
||||
"""Tests create volume qos, set, unset, show, delete"""
|
||||
|
||||
name = uuid.uuid4().hex
|
||||
cmd_output = self.openstack(
|
||||
'volume qos create '
|
||||
+ '--consumer front-end '
|
||||
+ '--property Alpha=a '
|
||||
+ name,
|
||||
parse_output=True,
|
||||
)
|
||||
self.addCleanup(self.openstack, 'volume qos delete ' + name)
|
||||
self.assertEqual(name, cmd_output['name'])
|
||||
|
||||
self.assertEqual("front-end", cmd_output['consumer'])
|
||||
|
||||
# Test volume qos set
|
||||
raw_output = self.openstack(
|
||||
'volume qos set '
|
||||
+ '--no-property '
|
||||
+ '--property Beta=b '
|
||||
+ '--property Charlie=c '
|
||||
+ name,
|
||||
)
|
||||
self.assertOutput('', raw_output)
|
||||
|
||||
# Test volume qos show
|
||||
cmd_output = self.openstack(
|
||||
'volume qos show ' + name,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual(name, cmd_output['name'])
|
||||
self.assertEqual(
|
||||
{'Beta': 'b', 'Charlie': 'c'},
|
||||
cmd_output['properties'],
|
||||
)
|
||||
|
||||
# Test volume qos unset
|
||||
raw_output = self.openstack(
|
||||
'volume qos unset ' + '--property Charlie ' + name,
|
||||
)
|
||||
self.assertOutput('', raw_output)
|
||||
|
||||
cmd_output = self.openstack(
|
||||
'volume qos show ' + name,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual(name, cmd_output['name'])
|
||||
self.assertEqual({'Beta': 'b'}, cmd_output['properties'])
|
||||
|
||||
# TODO(qiangjiahui): Add tests for associate and disassociate volume type
|
@ -1,76 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from openstackclient.tests.functional.volume.v1 import common
|
||||
|
||||
|
||||
class VolumeServiceTests(common.BaseVolumeTests):
|
||||
"""Functional tests for volume service."""
|
||||
|
||||
def test_volume_service_list(self):
|
||||
cmd_output = self.openstack('volume service list', parse_output=True)
|
||||
|
||||
# Get the nonredundant services and hosts
|
||||
services = list({x['Binary'] for x in cmd_output})
|
||||
|
||||
# Test volume service list --service
|
||||
cmd_output = self.openstack(
|
||||
'volume service list ' + '--service ' + services[0],
|
||||
parse_output=True,
|
||||
)
|
||||
for x in cmd_output:
|
||||
self.assertEqual(services[0], x['Binary'])
|
||||
|
||||
# TODO(zhiyong.dai): test volume service list --host after solving
|
||||
# https://bugs.launchpad.net/python-openstackclient/+bug/1664451
|
||||
|
||||
def test_volume_service_set(self):
|
||||
# Get a service and host
|
||||
cmd_output = self.openstack(
|
||||
'volume service list',
|
||||
parse_output=True,
|
||||
)
|
||||
service_1 = cmd_output[0]['Binary']
|
||||
host_1 = cmd_output[0]['Host']
|
||||
|
||||
# Test volume service set --enable
|
||||
raw_output = self.openstack(
|
||||
'volume service set --enable ' + host_1 + ' ' + service_1
|
||||
)
|
||||
self.assertOutput('', raw_output)
|
||||
|
||||
cmd_output = self.openstack(
|
||||
'volume service list --long',
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual('enabled', cmd_output[0]['Status'])
|
||||
self.assertIsNone(cmd_output[0]['Disabled Reason'])
|
||||
|
||||
# Test volume service set --disable and --disable-reason
|
||||
disable_reason = 'disable_reason'
|
||||
raw_output = self.openstack(
|
||||
'volume service set --disable '
|
||||
+ '--disable-reason '
|
||||
+ disable_reason
|
||||
+ ' '
|
||||
+ host_1
|
||||
+ ' '
|
||||
+ service_1
|
||||
)
|
||||
self.assertOutput('', raw_output)
|
||||
|
||||
cmd_output = self.openstack(
|
||||
'volume service list --long',
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual('disabled', cmd_output[0]['Status'])
|
||||
self.assertEqual(disable_reason, cmd_output[0]['Disabled Reason'])
|
@ -1,232 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import uuid
|
||||
|
||||
from openstackclient.tests.functional.volume.v1 import common
|
||||
|
||||
|
||||
class VolumeSnapshotTests(common.BaseVolumeTests):
|
||||
"""Functional tests for volume snapshot."""
|
||||
|
||||
VOLLY = uuid.uuid4().hex
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
# create a volume for all tests to create snapshot
|
||||
cmd_output = cls.openstack(
|
||||
'volume create ' + '--size 1 ' + cls.VOLLY,
|
||||
parse_output=True,
|
||||
)
|
||||
cls.wait_for_status('volume', cls.VOLLY, 'available')
|
||||
cls.VOLUME_ID = cmd_output['id']
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
try:
|
||||
cls.wait_for_status('volume', cls.VOLLY, 'available')
|
||||
raw_output = cls.openstack('volume delete --force ' + cls.VOLLY)
|
||||
cls.assertOutput('', raw_output)
|
||||
finally:
|
||||
super().tearDownClass()
|
||||
|
||||
def test_volume_snapshot_delete(self):
|
||||
"""Test create, delete multiple"""
|
||||
name1 = uuid.uuid4().hex
|
||||
cmd_output = self.openstack(
|
||||
'volume snapshot create ' + name1 + ' --volume ' + self.VOLLY,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual(
|
||||
name1,
|
||||
cmd_output["display_name"],
|
||||
)
|
||||
|
||||
name2 = uuid.uuid4().hex
|
||||
cmd_output = self.openstack(
|
||||
'volume snapshot create ' + name2 + ' --volume ' + self.VOLLY,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual(
|
||||
name2,
|
||||
cmd_output["display_name"],
|
||||
)
|
||||
|
||||
self.wait_for_status('volume snapshot', name1, 'available')
|
||||
self.wait_for_status('volume snapshot', name2, 'available')
|
||||
|
||||
del_output = self.openstack(
|
||||
'volume snapshot delete ' + name1 + ' ' + name2
|
||||
)
|
||||
self.assertOutput('', del_output)
|
||||
self.wait_for_delete('volume snapshot', name1)
|
||||
self.wait_for_delete('volume snapshot', name2)
|
||||
|
||||
def test_volume_snapshot_list(self):
|
||||
"""Test create, list filter"""
|
||||
name1 = uuid.uuid4().hex
|
||||
cmd_output = self.openstack(
|
||||
'volume snapshot create ' + name1 + ' --volume ' + self.VOLLY,
|
||||
parse_output=True,
|
||||
)
|
||||
self.addCleanup(self.wait_for_delete, 'volume snapshot', name1)
|
||||
self.addCleanup(self.openstack, 'volume snapshot delete ' + name1)
|
||||
self.assertEqual(
|
||||
name1,
|
||||
cmd_output["display_name"],
|
||||
)
|
||||
self.assertEqual(
|
||||
self.VOLUME_ID,
|
||||
cmd_output["volume_id"],
|
||||
)
|
||||
self.assertEqual(
|
||||
1,
|
||||
cmd_output["size"],
|
||||
)
|
||||
self.wait_for_status('volume snapshot', name1, 'available')
|
||||
|
||||
name2 = uuid.uuid4().hex
|
||||
cmd_output = self.openstack(
|
||||
'volume snapshot create ' + name2 + ' --volume ' + self.VOLLY,
|
||||
parse_output=True,
|
||||
)
|
||||
self.addCleanup(self.wait_for_delete, 'volume snapshot', name2)
|
||||
self.addCleanup(self.openstack, 'volume snapshot delete ' + name2)
|
||||
self.assertEqual(
|
||||
name2,
|
||||
cmd_output["display_name"],
|
||||
)
|
||||
self.assertEqual(
|
||||
self.VOLUME_ID,
|
||||
cmd_output["volume_id"],
|
||||
)
|
||||
self.assertEqual(
|
||||
1,
|
||||
cmd_output["size"],
|
||||
)
|
||||
self.wait_for_status('volume snapshot', name2, 'available')
|
||||
|
||||
# Test list --long, --status
|
||||
cmd_output = self.openstack(
|
||||
'volume snapshot list ' + '--long ' + '--status error',
|
||||
parse_output=True,
|
||||
)
|
||||
names = [x["Name"] for x in cmd_output]
|
||||
self.assertNotIn(name1, names)
|
||||
self.assertNotIn(name2, names)
|
||||
|
||||
# Test list --volume
|
||||
cmd_output = self.openstack(
|
||||
'volume snapshot list ' + '--volume ' + self.VOLLY,
|
||||
parse_output=True,
|
||||
)
|
||||
names = [x["Name"] for x in cmd_output]
|
||||
self.assertIn(name1, names)
|
||||
self.assertIn(name2, names)
|
||||
|
||||
# Test list --name
|
||||
cmd_output = self.openstack(
|
||||
'volume snapshot list ' + '--name ' + name1,
|
||||
parse_output=True,
|
||||
)
|
||||
names = [x["Name"] for x in cmd_output]
|
||||
self.assertIn(name1, names)
|
||||
self.assertNotIn(name2, names)
|
||||
|
||||
def test_snapshot_set(self):
|
||||
"""Test create, set, unset, show, delete volume snapshot"""
|
||||
name = uuid.uuid4().hex
|
||||
new_name = name + "_"
|
||||
cmd_output = self.openstack(
|
||||
'volume snapshot create '
|
||||
+ '--volume '
|
||||
+ self.VOLLY
|
||||
+ ' --description aaaa '
|
||||
+ name,
|
||||
parse_output=True,
|
||||
)
|
||||
self.addCleanup(self.wait_for_delete, 'volume snapshot', new_name)
|
||||
self.addCleanup(self.openstack, 'volume snapshot delete ' + new_name)
|
||||
self.assertEqual(
|
||||
name,
|
||||
cmd_output["display_name"],
|
||||
)
|
||||
self.assertEqual(
|
||||
1,
|
||||
cmd_output["size"],
|
||||
)
|
||||
self.assertEqual(
|
||||
'aaaa',
|
||||
cmd_output["display_description"],
|
||||
)
|
||||
self.wait_for_status('volume snapshot', name, 'available')
|
||||
|
||||
# Test volume snapshot set
|
||||
raw_output = self.openstack(
|
||||
'volume snapshot set '
|
||||
+ '--name '
|
||||
+ new_name
|
||||
+ ' --description bbbb '
|
||||
+ '--property Alpha=a '
|
||||
+ '--property Beta=b '
|
||||
+ name,
|
||||
)
|
||||
self.assertOutput('', raw_output)
|
||||
|
||||
# Show snapshot set result
|
||||
cmd_output = self.openstack(
|
||||
'volume snapshot show ' + new_name,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual(
|
||||
new_name,
|
||||
cmd_output["display_name"],
|
||||
)
|
||||
self.assertEqual(
|
||||
1,
|
||||
cmd_output["size"],
|
||||
)
|
||||
self.assertEqual(
|
||||
'bbbb',
|
||||
cmd_output["display_description"],
|
||||
)
|
||||
self.assertEqual(
|
||||
{'Alpha': 'a', 'Beta': 'b'},
|
||||
cmd_output["properties"],
|
||||
)
|
||||
|
||||
# Test volume unset
|
||||
raw_output = self.openstack(
|
||||
'volume snapshot unset ' + '--property Alpha ' + new_name,
|
||||
)
|
||||
self.assertOutput('', raw_output)
|
||||
|
||||
cmd_output = self.openstack(
|
||||
'volume snapshot show ' + new_name,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual(
|
||||
{'Beta': 'b'},
|
||||
cmd_output["properties"],
|
||||
)
|
||||
|
||||
# Test volume snapshot set --no-property
|
||||
raw_output = self.openstack(
|
||||
'volume snapshot set ' + '--no-property ' + new_name,
|
||||
)
|
||||
self.assertOutput('', raw_output)
|
||||
cmd_output = self.openstack(
|
||||
'volume snapshot show ' + new_name,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual({}, cmd_output["properties"])
|
@ -1,111 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import uuid
|
||||
|
||||
from openstackclient.tests.functional.volume.v1 import common
|
||||
|
||||
|
||||
class TransferRequestTests(common.BaseVolumeTests):
|
||||
"""Functional tests for transfer request."""
|
||||
|
||||
NAME = uuid.uuid4().hex
|
||||
VOLUME_NAME = uuid.uuid4().hex
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cmd_output = cls.openstack(
|
||||
'volume create --size 1 ' + cls.VOLUME_NAME,
|
||||
parse_output=True,
|
||||
)
|
||||
cls.assertOutput(cls.VOLUME_NAME, cmd_output['name'])
|
||||
|
||||
cls.wait_for_status("volume", cls.VOLUME_NAME, "available")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
try:
|
||||
raw_output_volume = cls.openstack(
|
||||
'volume delete ' + cls.VOLUME_NAME
|
||||
)
|
||||
cls.assertOutput('', raw_output_volume)
|
||||
finally:
|
||||
super().tearDownClass()
|
||||
|
||||
def test_volume_transfer_request_accept(self):
|
||||
volume_name = uuid.uuid4().hex
|
||||
name = uuid.uuid4().hex
|
||||
|
||||
# create a volume
|
||||
cmd_output = self.openstack(
|
||||
'volume create --size 1 ' + volume_name,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual(volume_name, cmd_output['name'])
|
||||
|
||||
# create volume transfer request for the volume
|
||||
# and get the auth_key of the new transfer request
|
||||
cmd_output = self.openstack(
|
||||
'volume transfer request create '
|
||||
+ volume_name
|
||||
+ ' --name '
|
||||
+ name,
|
||||
parse_output=True,
|
||||
)
|
||||
auth_key = cmd_output['auth_key']
|
||||
self.assertTrue(auth_key)
|
||||
|
||||
# accept the volume transfer request
|
||||
output = self.openstack(
|
||||
'volume transfer request accept '
|
||||
+ name
|
||||
+ ' '
|
||||
+ '--auth-key '
|
||||
+ auth_key,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual(name, output.get('name'))
|
||||
|
||||
# the volume transfer will be removed by default after accepted
|
||||
# so just need to delete the volume here
|
||||
raw_output = self.openstack('volume delete ' + volume_name)
|
||||
self.assertEqual('', raw_output)
|
||||
|
||||
def test_volume_transfer_request_list_show(self):
|
||||
name = uuid.uuid4().hex
|
||||
cmd_output = self.openstack(
|
||||
'volume transfer request create '
|
||||
+ ' --name '
|
||||
+ name
|
||||
+ ' '
|
||||
+ self.VOLUME_NAME,
|
||||
parse_output=True,
|
||||
)
|
||||
self.addCleanup(
|
||||
self.openstack, 'volume transfer request delete ' + name
|
||||
)
|
||||
self.assertOutput(name, cmd_output['name'])
|
||||
auth_key = cmd_output['auth_key']
|
||||
self.assertTrue(auth_key)
|
||||
|
||||
cmd_output = self.openstack(
|
||||
'volume transfer request list',
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertIn(name, [req['Name'] for req in cmd_output])
|
||||
|
||||
cmd_output = self.openstack(
|
||||
'volume transfer request show ' + name,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual(name, cmd_output['name'])
|
@ -1,228 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import uuid
|
||||
|
||||
from openstackclient.tests.functional.volume.v1 import common
|
||||
|
||||
|
||||
class VolumeTests(common.BaseVolumeTests):
|
||||
"""Functional tests for volume."""
|
||||
|
||||
def test_volume_create_and_delete(self):
|
||||
"""Test create, delete multiple"""
|
||||
name1 = uuid.uuid4().hex
|
||||
cmd_output = self.openstack(
|
||||
'volume create ' + '--size 1 ' + name1,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual(
|
||||
1,
|
||||
cmd_output["size"],
|
||||
)
|
||||
|
||||
name2 = uuid.uuid4().hex
|
||||
cmd_output = self.openstack(
|
||||
'volume create ' + '--size 2 ' + name2,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual(
|
||||
2,
|
||||
cmd_output["size"],
|
||||
)
|
||||
|
||||
self.wait_for_status("volume", name1, "available")
|
||||
self.wait_for_status("volume", name2, "available")
|
||||
del_output = self.openstack('volume delete ' + name1 + ' ' + name2)
|
||||
self.assertOutput('', del_output)
|
||||
|
||||
def test_volume_list(self):
|
||||
"""Test create, list filter"""
|
||||
name1 = uuid.uuid4().hex
|
||||
cmd_output = self.openstack(
|
||||
'volume create ' + '--size 1 ' + name1,
|
||||
parse_output=True,
|
||||
)
|
||||
self.addCleanup(self.openstack, 'volume delete ' + name1)
|
||||
self.assertEqual(
|
||||
1,
|
||||
cmd_output["size"],
|
||||
)
|
||||
self.wait_for_status("volume", name1, "available")
|
||||
|
||||
name2 = uuid.uuid4().hex
|
||||
cmd_output = self.openstack(
|
||||
'volume create ' + '--size 2 ' + name2,
|
||||
parse_output=True,
|
||||
)
|
||||
self.addCleanup(self.openstack, 'volume delete ' + name2)
|
||||
self.assertEqual(
|
||||
2,
|
||||
cmd_output["size"],
|
||||
)
|
||||
self.wait_for_status("volume", name2, "available")
|
||||
|
||||
# Test list
|
||||
cmd_output = self.openstack(
|
||||
'volume list ',
|
||||
parse_output=True,
|
||||
)
|
||||
names = [x["Name"] for x in cmd_output]
|
||||
self.assertIn(name1, names)
|
||||
self.assertIn(name2, names)
|
||||
|
||||
# Test list --long
|
||||
cmd_output = self.openstack(
|
||||
'volume list --long',
|
||||
parse_output=True,
|
||||
)
|
||||
bootable = [x["Bootable"] for x in cmd_output]
|
||||
self.assertIn('false', bootable)
|
||||
|
||||
# Test list --name
|
||||
cmd_output = self.openstack(
|
||||
'volume list ' + '--name ' + name1,
|
||||
parse_output=True,
|
||||
)
|
||||
names = [x["Name"] for x in cmd_output]
|
||||
self.assertIn(name1, names)
|
||||
self.assertNotIn(name2, names)
|
||||
|
||||
def test_volume_set_and_unset(self):
|
||||
"""Tests create volume, set, unset, show, delete"""
|
||||
name = uuid.uuid4().hex
|
||||
cmd_output = self.openstack(
|
||||
'volume create '
|
||||
+ '--size 1 '
|
||||
+ '--description aaaa '
|
||||
+ '--property Alpha=a '
|
||||
+ name,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual(
|
||||
name,
|
||||
cmd_output["name"],
|
||||
)
|
||||
self.assertEqual(
|
||||
1,
|
||||
cmd_output["size"],
|
||||
)
|
||||
self.assertEqual(
|
||||
'aaaa',
|
||||
cmd_output["display_description"],
|
||||
)
|
||||
self.assertEqual(
|
||||
{'Alpha': 'a'},
|
||||
cmd_output["properties"],
|
||||
)
|
||||
self.assertEqual(
|
||||
'false',
|
||||
cmd_output["bootable"],
|
||||
)
|
||||
self.wait_for_status("volume", name, "available")
|
||||
|
||||
# Test volume set
|
||||
new_name = uuid.uuid4().hex
|
||||
self.addCleanup(self.openstack, 'volume delete ' + new_name)
|
||||
raw_output = self.openstack(
|
||||
'volume set '
|
||||
+ '--name '
|
||||
+ new_name
|
||||
+ ' --size 2 '
|
||||
+ '--description bbbb '
|
||||
+ '--no-property '
|
||||
+ '--property Beta=b '
|
||||
+ '--property Gamma=c '
|
||||
+ '--bootable '
|
||||
+ name,
|
||||
)
|
||||
self.assertOutput('', raw_output)
|
||||
|
||||
cmd_output = self.openstack(
|
||||
'volume show ' + new_name,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual(
|
||||
new_name,
|
||||
cmd_output["name"],
|
||||
)
|
||||
self.assertEqual(
|
||||
2,
|
||||
cmd_output["size"],
|
||||
)
|
||||
self.assertEqual(
|
||||
'bbbb',
|
||||
cmd_output["display_description"],
|
||||
)
|
||||
self.assertEqual(
|
||||
{'Beta': 'b', 'Gamma': 'c'},
|
||||
cmd_output["properties"],
|
||||
)
|
||||
self.assertEqual(
|
||||
'true',
|
||||
cmd_output["bootable"],
|
||||
)
|
||||
|
||||
# Test volume unset
|
||||
raw_output = self.openstack(
|
||||
'volume unset ' + '--property Beta ' + new_name,
|
||||
)
|
||||
self.assertOutput('', raw_output)
|
||||
|
||||
cmd_output = self.openstack(
|
||||
'volume show ' + new_name,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual(
|
||||
{'Gamma': 'c'},
|
||||
cmd_output["properties"],
|
||||
)
|
||||
|
||||
def test_volume_create_and_list_and_show_backward_compatibility(self):
|
||||
"""Test backward compatibility of create, list, show"""
|
||||
name1 = uuid.uuid4().hex
|
||||
output = self.openstack(
|
||||
'volume create ' + '-c display_name -c id ' + '--size 1 ' + name1,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertIn('display_name', output)
|
||||
self.assertEqual(name1, output['display_name'])
|
||||
self.assertIn('id', output)
|
||||
volume_id = output['id']
|
||||
self.assertIsNotNone(volume_id)
|
||||
self.assertNotIn('name', output)
|
||||
self.addCleanup(self.openstack, 'volume delete ' + volume_id)
|
||||
|
||||
self.wait_for_status("volume", name1, "available")
|
||||
|
||||
output = self.openstack(
|
||||
'volume list ' + '-c "Display Name"',
|
||||
parse_output=True,
|
||||
)
|
||||
for each_volume in output:
|
||||
self.assertIn('Display Name', each_volume)
|
||||
|
||||
output = self.openstack(
|
||||
'volume list ' + '-c "Name"',
|
||||
parse_output=True,
|
||||
)
|
||||
for each_volume in output:
|
||||
self.assertIn('Name', each_volume)
|
||||
|
||||
output = self.openstack(
|
||||
'volume show ' + '-c display_name -c id ' + name1,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertIn('display_name', output)
|
||||
self.assertEqual(name1, output['display_name'])
|
||||
self.assertIn('id', output)
|
||||
self.assertNotIn('name', output)
|
@ -1,213 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from openstackclient.tests.functional.volume.v1 import common
|
||||
|
||||
|
||||
class VolumeTypeTests(common.BaseVolumeTests):
|
||||
"""Functional tests for volume type."""
|
||||
|
||||
def test_volume_type_create_list(self):
|
||||
name = uuid.uuid4().hex
|
||||
cmd_output = self.openstack(
|
||||
'volume type create --private ' + name,
|
||||
parse_output=True,
|
||||
)
|
||||
self.addCleanup(
|
||||
self.openstack,
|
||||
'volume type delete ' + name,
|
||||
)
|
||||
self.assertEqual(name, cmd_output['name'])
|
||||
|
||||
cmd_output = self.openstack(
|
||||
f'volume type show {name}',
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual(self.NAME, cmd_output['name'])
|
||||
|
||||
cmd_output = self.openstack('volume type list', parse_output=True)
|
||||
self.assertIn(self.NAME, [t['Name'] for t in cmd_output])
|
||||
|
||||
cmd_output = self.openstack(
|
||||
'volume type list --default',
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual(1, len(cmd_output))
|
||||
self.assertEqual('lvmdriver-1', cmd_output[0]['Name'])
|
||||
|
||||
def test_volume_type_set_unset_properties(self):
|
||||
name = uuid.uuid4().hex
|
||||
cmd_output = self.openstack(
|
||||
'volume type create --private ' + name,
|
||||
parse_output=True,
|
||||
)
|
||||
self.addCleanup(self.openstack, 'volume type delete ' + name)
|
||||
self.assertEqual(name, cmd_output['name'])
|
||||
|
||||
raw_output = self.openstack(
|
||||
f'volume type set --property a=b --property c=d {name}'
|
||||
)
|
||||
self.assertEqual("", raw_output)
|
||||
cmd_output = self.openstack(
|
||||
f'volume type show {name}',
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual({'a': 'b', 'c': 'd'}, cmd_output['properties'])
|
||||
|
||||
raw_output = self.openstack(f'volume type unset --property a {name}')
|
||||
self.assertEqual("", raw_output)
|
||||
cmd_output = self.openstack(
|
||||
f'volume type show {name}',
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual({'c': 'd'}, cmd_output['properties'])
|
||||
|
||||
def test_volume_type_set_unset_multiple_properties(self):
|
||||
name = uuid.uuid4().hex
|
||||
cmd_output = self.openstack(
|
||||
'volume type create --private ' + name,
|
||||
parse_output=True,
|
||||
)
|
||||
self.addCleanup(self.openstack, 'volume type delete ' + name)
|
||||
self.assertEqual(name, cmd_output['name'])
|
||||
|
||||
raw_output = self.openstack(
|
||||
f'volume type set --property a=b --property c=d {name}'
|
||||
)
|
||||
self.assertEqual("", raw_output)
|
||||
cmd_output = self.openstack(
|
||||
f'volume type show {name}',
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual({'a': 'b', 'c': 'd'}, cmd_output['properties'])
|
||||
|
||||
raw_output = self.openstack(
|
||||
f'volume type unset --property a --property c {name}'
|
||||
)
|
||||
self.assertEqual("", raw_output)
|
||||
cmd_output = self.openstack(
|
||||
f'volume type show {name}',
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual({}, cmd_output['properties'])
|
||||
|
||||
def test_multi_delete(self):
|
||||
vol_type1 = uuid.uuid4().hex
|
||||
vol_type2 = uuid.uuid4().hex
|
||||
self.openstack(f'volume type create {vol_type1}')
|
||||
time.sleep(5)
|
||||
self.openstack(f'volume type create {vol_type2}')
|
||||
time.sleep(5)
|
||||
cmd = f'volume type delete {vol_type1} {vol_type2}'
|
||||
raw_output = self.openstack(cmd)
|
||||
self.assertOutput('', raw_output)
|
||||
|
||||
# NOTE: Add some basic functional tests with the old format to
|
||||
# make sure the command works properly, need to change
|
||||
# these to new test format when beef up all tests for
|
||||
# volume type commands.
|
||||
def test_encryption_type(self):
|
||||
encryption_type = uuid.uuid4().hex
|
||||
# test create new encryption type
|
||||
cmd_output = self.openstack(
|
||||
'volume type create '
|
||||
'--encryption-provider LuksEncryptor '
|
||||
'--encryption-cipher aes-xts-plain64 '
|
||||
'--encryption-key-size 128 '
|
||||
'--encryption-control-location front-end ' + encryption_type
|
||||
)
|
||||
expected = {
|
||||
'provider': 'LuksEncryptor',
|
||||
'cipher': 'aes-xts-plain64',
|
||||
'key_size': 128,
|
||||
'control_location': 'front-end',
|
||||
}
|
||||
for attr, value in expected.items():
|
||||
self.assertEqual(value, cmd_output['encryption'][attr])
|
||||
# test show encryption type
|
||||
cmd_output = self.openstack(
|
||||
'volume type show --encryption-type ' + encryption_type,
|
||||
parse_output=True,
|
||||
)
|
||||
expected = {
|
||||
'provider': 'LuksEncryptor',
|
||||
'cipher': 'aes-xts-plain64',
|
||||
'key_size': 128,
|
||||
'control_location': 'front-end',
|
||||
}
|
||||
for attr, value in expected.items():
|
||||
self.assertEqual(value, cmd_output['encryption'][attr])
|
||||
# test list encryption type
|
||||
cmd_output = self.openstack(
|
||||
'volume type list --encryption-type',
|
||||
parse_output=True,
|
||||
)
|
||||
encryption_output = [
|
||||
t['Encryption'] for t in cmd_output if t['Name'] == encryption_type
|
||||
][0]
|
||||
expected = {
|
||||
'provider': 'LuksEncryptor',
|
||||
'cipher': 'aes-xts-plain64',
|
||||
'key_size': 128,
|
||||
'control_location': 'front-end',
|
||||
}
|
||||
for attr, value in expected.items():
|
||||
self.assertEqual(value, encryption_output[attr])
|
||||
# test set new encryption type
|
||||
raw_output = self.openstack(
|
||||
'volume type set '
|
||||
'--encryption-provider LuksEncryptor '
|
||||
'--encryption-cipher aes-xts-plain64 '
|
||||
'--encryption-key-size 128 '
|
||||
'--encryption-control-location front-end ' + self.NAME
|
||||
)
|
||||
self.assertEqual('', raw_output)
|
||||
|
||||
name = uuid.uuid4().hex
|
||||
cmd_output = self.openstack(
|
||||
'volume type create --private ' + name,
|
||||
parse_output=True,
|
||||
)
|
||||
self.addCleanup(
|
||||
self.openstack,
|
||||
'volume type delete ' + name,
|
||||
)
|
||||
self.assertEqual(name, cmd_output['name'])
|
||||
|
||||
cmd_output = self.openstack(
|
||||
'volume type show --encryption-type ' + name,
|
||||
parse_output=True,
|
||||
)
|
||||
expected = {
|
||||
'provider': 'LuksEncryptor',
|
||||
'cipher': 'aes-xts-plain64',
|
||||
'key_size': 128,
|
||||
'control_location': 'front-end',
|
||||
}
|
||||
for attr, value in expected.items():
|
||||
self.assertEqual(value, cmd_output['encryption'][attr])
|
||||
# test unset encryption type
|
||||
raw_output = self.openstack(
|
||||
'volume type unset --encryption-type ' + name
|
||||
)
|
||||
self.assertEqual('', raw_output)
|
||||
cmd_output = self.openstack(
|
||||
'volume type show --encryption-type ' + name,
|
||||
parse_output=True,
|
||||
)
|
||||
self.assertEqual({}, cmd_output['encryption'])
|
||||
# test delete encryption type
|
||||
raw_output = self.openstack('volume type delete ' + encryption_type)
|
||||
self.assertEqual('', raw_output)
|
@ -20,7 +20,7 @@ from openstack.image.v1 import image
|
||||
|
||||
from openstackclient.tests.unit import fakes
|
||||
from openstackclient.tests.unit import utils
|
||||
from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes
|
||||
from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes
|
||||
|
||||
|
||||
class FakeClientMixin:
|
||||
@ -35,7 +35,7 @@ class TestImagev1(FakeClientMixin, utils.TestCommand):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.app.client_manager.volume = volume_fakes.FakeVolumev1Client(
|
||||
self.app.client_manager.volume = volume_fakes.FakeVolumeClient(
|
||||
endpoint=fakes.AUTH_URL,
|
||||
token=fakes.AUTH_TOKEN,
|
||||
)
|
||||
|
@ -1,615 +0,0 @@
|
||||
# Copyright 2013 Nebula Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
import copy
|
||||
import random
|
||||
from unittest import mock
|
||||
import uuid
|
||||
|
||||
from openstack.image.v1 import _proxy as image_v1_proxy
|
||||
|
||||
from openstackclient.tests.unit import fakes
|
||||
from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes
|
||||
from openstackclient.tests.unit import utils
|
||||
|
||||
|
||||
class FakeVolumev1Client:
|
||||
def __init__(self, **kwargs):
|
||||
self.volumes = mock.Mock()
|
||||
self.volumes.resource_class = fakes.FakeResource(None, {})
|
||||
self.services = mock.Mock()
|
||||
self.services.resource_class = fakes.FakeResource(None, {})
|
||||
self.extensions = mock.Mock()
|
||||
self.extensions.resource_class = fakes.FakeResource(None, {})
|
||||
self.qos_specs = mock.Mock()
|
||||
self.qos_specs.resource_class = fakes.FakeResource(None, {})
|
||||
self.volume_types = mock.Mock()
|
||||
self.volume_types.resource_class = fakes.FakeResource(None, {})
|
||||
self.volume_encryption_types = mock.Mock()
|
||||
self.volume_encryption_types.resource_class = fakes.FakeResource(
|
||||
None, {}
|
||||
)
|
||||
self.transfers = mock.Mock()
|
||||
self.transfers.resource_class = fakes.FakeResource(None, {})
|
||||
self.volume_snapshots = mock.Mock()
|
||||
self.volume_snapshots.resource_class = fakes.FakeResource(None, {})
|
||||
self.backups = mock.Mock()
|
||||
self.backups.resource_class = fakes.FakeResource(None, {})
|
||||
self.restores = mock.Mock()
|
||||
self.restores.resource_class = fakes.FakeResource(None, {})
|
||||
self.auth_token = kwargs['token']
|
||||
self.management_url = kwargs['endpoint']
|
||||
|
||||
|
||||
class FakeClientMixin:
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.app.client_manager.volume = FakeVolumev1Client(
|
||||
endpoint=fakes.AUTH_URL,
|
||||
token=fakes.AUTH_TOKEN,
|
||||
)
|
||||
self.volume_client = self.app.client_manager.volume
|
||||
|
||||
|
||||
class TestVolumev1(
|
||||
identity_fakes.FakeClientMixin,
|
||||
FakeClientMixin,
|
||||
utils.TestCommand,
|
||||
):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
# avoid circular imports by defining this manually rather than using
|
||||
# openstackclient.tests.unit.image.v1.fakes.FakeClientMixin
|
||||
self.app.client_manager.image = mock.Mock(spec=image_v1_proxy.Proxy)
|
||||
self.image_client = self.app.client_manager.image
|
||||
|
||||
|
||||
def create_one_transfer(attrs=None):
|
||||
"""Create a fake transfer.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes of Transfer Request
|
||||
:return:
|
||||
A FakeResource object with volume_id, name, id.
|
||||
"""
|
||||
# Set default attribute
|
||||
transfer_info = {
|
||||
'volume_id': 'volume-id-' + uuid.uuid4().hex,
|
||||
'name': 'fake_transfer_name',
|
||||
'id': 'id-' + uuid.uuid4().hex,
|
||||
'links': 'links-' + uuid.uuid4().hex,
|
||||
}
|
||||
|
||||
# Overwrite default attributes if there are some attributes set
|
||||
attrs = attrs or {}
|
||||
|
||||
transfer_info.update(attrs)
|
||||
|
||||
transfer = fakes.FakeResource(None, transfer_info, loaded=True)
|
||||
|
||||
return transfer
|
||||
|
||||
|
||||
def create_transfers(attrs=None, count=2):
|
||||
"""Create multiple fake transfers.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes of transfer
|
||||
:param Integer count:
|
||||
The number of transfers to be faked
|
||||
:return:
|
||||
A list of FakeResource objects
|
||||
"""
|
||||
transfers = []
|
||||
for n in range(0, count):
|
||||
transfers.append(create_one_transfer(attrs))
|
||||
|
||||
return transfers
|
||||
|
||||
|
||||
def get_transfers(transfers=None, count=2):
|
||||
"""Get an iterable MagicMock object with a list of faked transfers.
|
||||
|
||||
If transfers list is provided, then initialize the Mock object with the
|
||||
list. Otherwise create one.
|
||||
|
||||
:param List transfers:
|
||||
A list of FakeResource objects faking transfers
|
||||
:param Integer count:
|
||||
The number of transfers to be faked
|
||||
:return
|
||||
An iterable Mock object with side_effect set to a list of faked
|
||||
transfers
|
||||
"""
|
||||
if transfers is None:
|
||||
transfers = create_transfers(count)
|
||||
|
||||
return mock.Mock(side_effect=transfers)
|
||||
|
||||
|
||||
def create_one_service(attrs=None):
|
||||
"""Create a fake service.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes of service
|
||||
:return:
|
||||
A FakeResource object with host, status, etc.
|
||||
"""
|
||||
# Set default attribute
|
||||
service_info = {
|
||||
'host': 'host_test',
|
||||
'binary': 'cinder_test',
|
||||
'status': 'enabled',
|
||||
'disabled_reason': 'LongHoliday-GoldenWeek',
|
||||
'zone': 'fake_zone',
|
||||
'updated_at': 'fake_date',
|
||||
'state': 'fake_state',
|
||||
}
|
||||
|
||||
# Overwrite default attributes if there are some attributes set
|
||||
attrs = attrs or {}
|
||||
|
||||
service_info.update(attrs)
|
||||
|
||||
service = fakes.FakeResource(None, service_info, loaded=True)
|
||||
|
||||
return service
|
||||
|
||||
|
||||
def create_services(attrs=None, count=2):
|
||||
"""Create multiple fake services.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes of service
|
||||
:param Integer count:
|
||||
The number of services to be faked
|
||||
:return:
|
||||
A list of FakeResource objects
|
||||
"""
|
||||
services = []
|
||||
for n in range(0, count):
|
||||
services.append(create_one_service(attrs))
|
||||
|
||||
return services
|
||||
|
||||
|
||||
def get_services(services=None, count=2):
|
||||
"""Get an iterable MagicMock object with a list of faked services.
|
||||
|
||||
If services list is provided, then initialize the Mock object with the
|
||||
list. Otherwise create one.
|
||||
|
||||
:param List services:
|
||||
A list of FakeResource objects faking services
|
||||
:param Integer count:
|
||||
The number of services to be faked
|
||||
:return
|
||||
An iterable Mock object with side_effect set to a list of faked
|
||||
services
|
||||
"""
|
||||
if services is None:
|
||||
services = create_services(count)
|
||||
|
||||
return mock.Mock(side_effect=services)
|
||||
|
||||
|
||||
def create_one_qos(attrs=None):
|
||||
"""Create a fake Qos specification.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes
|
||||
:return:
|
||||
A FakeResource object with id, name, consumer, etc.
|
||||
"""
|
||||
attrs = attrs or {}
|
||||
|
||||
# Set default attributes.
|
||||
qos_info = {
|
||||
"id": 'qos-id-' + uuid.uuid4().hex,
|
||||
"name": 'qos-name-' + uuid.uuid4().hex,
|
||||
"consumer": 'front-end',
|
||||
"specs": {"foo": "bar", "iops": "9001"},
|
||||
}
|
||||
|
||||
# Overwrite default attributes.
|
||||
qos_info.update(attrs)
|
||||
|
||||
qos = fakes.FakeResource(info=copy.deepcopy(qos_info), loaded=True)
|
||||
return qos
|
||||
|
||||
|
||||
def create_one_qos_association(attrs=None):
|
||||
"""Create a fake Qos specification association.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes
|
||||
:return:
|
||||
A FakeResource object with id, name, association_type, etc.
|
||||
"""
|
||||
attrs = attrs or {}
|
||||
|
||||
# Set default attributes.
|
||||
qos_association_info = {
|
||||
"id": 'type-id-' + uuid.uuid4().hex,
|
||||
"name": 'type-name-' + uuid.uuid4().hex,
|
||||
"association_type": 'volume_type',
|
||||
}
|
||||
|
||||
# Overwrite default attributes.
|
||||
qos_association_info.update(attrs)
|
||||
|
||||
qos_association = fakes.FakeResource(
|
||||
info=copy.deepcopy(qos_association_info), loaded=True
|
||||
)
|
||||
return qos_association
|
||||
|
||||
|
||||
def create_qoses(attrs=None, count=2):
|
||||
"""Create multiple fake Qos specifications.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes
|
||||
:param int count:
|
||||
The number of Qos specifications to fake
|
||||
:return:
|
||||
A list of FakeResource objects faking the Qos specifications
|
||||
"""
|
||||
qoses = []
|
||||
for i in range(0, count):
|
||||
qos = create_one_qos(attrs)
|
||||
qoses.append(qos)
|
||||
|
||||
return qoses
|
||||
|
||||
|
||||
def get_qoses(qoses=None, count=2):
|
||||
"""Get an iterable MagicMock object with a list of faked qoses.
|
||||
|
||||
If qoses list is provided, then initialize the Mock object with the
|
||||
list. Otherwise create one.
|
||||
|
||||
:param List volumes:
|
||||
A list of FakeResource objects faking qoses
|
||||
:param Integer count:
|
||||
The number of qoses to be faked
|
||||
:return
|
||||
An iterable Mock object with side_effect set to a list of faked
|
||||
qoses
|
||||
"""
|
||||
if qoses is None:
|
||||
qoses = create_qoses(count)
|
||||
|
||||
return mock.Mock(side_effect=qoses)
|
||||
|
||||
|
||||
def create_one_volume(attrs=None):
|
||||
"""Create a fake volume.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes of volume
|
||||
:return:
|
||||
A FakeResource object with id, name, status, etc.
|
||||
"""
|
||||
attrs = attrs or {}
|
||||
|
||||
# Set default attribute
|
||||
volume_info = {
|
||||
'id': 'volume-id' + uuid.uuid4().hex,
|
||||
'display_name': 'volume-name' + uuid.uuid4().hex,
|
||||
'display_description': 'description' + uuid.uuid4().hex,
|
||||
'status': 'available',
|
||||
'size': 10,
|
||||
'volume_type': random.choice(['fake_lvmdriver-1', 'fake_lvmdriver-2']),
|
||||
'bootable': 'true',
|
||||
'metadata': {
|
||||
'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex,
|
||||
'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex,
|
||||
'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex,
|
||||
},
|
||||
'snapshot_id': 'snapshot-id-' + uuid.uuid4().hex,
|
||||
'availability_zone': 'zone' + uuid.uuid4().hex,
|
||||
'attachments': [
|
||||
{
|
||||
'device': '/dev/' + uuid.uuid4().hex,
|
||||
'server_id': uuid.uuid4().hex,
|
||||
},
|
||||
],
|
||||
'created_at': 'time-' + uuid.uuid4().hex,
|
||||
}
|
||||
|
||||
# Overwrite default attributes if there are some attributes set
|
||||
volume_info.update(attrs)
|
||||
|
||||
volume = fakes.FakeResource(None, volume_info, loaded=True)
|
||||
return volume
|
||||
|
||||
|
||||
def create_volumes(attrs=None, count=2):
|
||||
"""Create multiple fake volumes.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes of volume
|
||||
:param Integer count:
|
||||
The number of volumes to be faked
|
||||
:return:
|
||||
A list of FakeResource objects
|
||||
"""
|
||||
volumes = []
|
||||
for n in range(0, count):
|
||||
volumes.append(create_one_volume(attrs))
|
||||
|
||||
return volumes
|
||||
|
||||
|
||||
def get_volumes(volumes=None, count=2):
|
||||
"""Get an iterable MagicMock object with a list of faked volumes.
|
||||
|
||||
If volumes list is provided, then initialize the Mock object with the
|
||||
list. Otherwise create one.
|
||||
|
||||
:param List volumes:
|
||||
A list of FakeResource objects faking volumes
|
||||
:param Integer count:
|
||||
The number of volumes to be faked
|
||||
:return
|
||||
An iterable Mock object with side_effect set to a list of faked
|
||||
volumes
|
||||
"""
|
||||
if volumes is None:
|
||||
volumes = create_volumes(count)
|
||||
|
||||
return mock.Mock(side_effect=volumes)
|
||||
|
||||
|
||||
def create_one_volume_type(attrs=None, methods=None):
|
||||
"""Create a fake volume type.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes
|
||||
:param Dictionary methods:
|
||||
A dictionary with all methods
|
||||
:return:
|
||||
A FakeResource object with id, name, description, etc.
|
||||
"""
|
||||
attrs = attrs or {}
|
||||
methods = methods or {}
|
||||
|
||||
# Set default attributes.
|
||||
volume_type_info = {
|
||||
"id": 'type-id-' + uuid.uuid4().hex,
|
||||
"name": 'type-name-' + uuid.uuid4().hex,
|
||||
"description": 'type-description-' + uuid.uuid4().hex,
|
||||
"extra_specs": {"foo": "bar"},
|
||||
"is_public": True,
|
||||
}
|
||||
|
||||
# Overwrite default attributes.
|
||||
volume_type_info.update(attrs)
|
||||
|
||||
volume_type = fakes.FakeResource(
|
||||
info=copy.deepcopy(volume_type_info), methods=methods, loaded=True
|
||||
)
|
||||
return volume_type
|
||||
|
||||
|
||||
def create_volume_types(attrs=None, count=2):
|
||||
"""Create multiple fake types.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes
|
||||
:param int count:
|
||||
The number of types to fake
|
||||
:return:
|
||||
A list of FakeResource objects faking the types
|
||||
"""
|
||||
volume_types = []
|
||||
for i in range(0, count):
|
||||
volume_type = create_one_volume_type(attrs)
|
||||
volume_types.append(volume_type)
|
||||
|
||||
return volume_types
|
||||
|
||||
|
||||
def get_volume_types(volume_types=None, count=2):
|
||||
"""Get an iterable MagicMock object with a list of faked types.
|
||||
|
||||
If types list is provided, then initialize the Mock object with the
|
||||
list. Otherwise create one.
|
||||
|
||||
:param List volume_types:
|
||||
A list of FakeResource objects faking types
|
||||
:param Integer count:
|
||||
The number of types to be faked
|
||||
:return
|
||||
An iterable Mock object with side_effect set to a list of faked
|
||||
types
|
||||
"""
|
||||
if volume_types is None:
|
||||
volume_types = create_volume_types(count)
|
||||
|
||||
return mock.Mock(side_effect=volume_types)
|
||||
|
||||
|
||||
def create_one_encryption_volume_type(attrs=None):
|
||||
"""Create a fake encryption volume type.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes
|
||||
:return:
|
||||
A FakeResource object with volume_type_id etc.
|
||||
"""
|
||||
attrs = attrs or {}
|
||||
|
||||
# Set default attributes.
|
||||
encryption_info = {
|
||||
"volume_type_id": 'type-id-' + uuid.uuid4().hex,
|
||||
'provider': 'LuksEncryptor',
|
||||
'cipher': None,
|
||||
'key_size': None,
|
||||
'control_location': 'front-end',
|
||||
}
|
||||
|
||||
# Overwrite default attributes.
|
||||
encryption_info.update(attrs)
|
||||
|
||||
encryption_type = fakes.FakeResource(
|
||||
info=copy.deepcopy(encryption_info), loaded=True
|
||||
)
|
||||
return encryption_type
|
||||
|
||||
|
||||
def create_one_snapshot(attrs=None):
|
||||
"""Create a fake snapshot.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes
|
||||
:return:
|
||||
A FakeResource object with id, name, description, etc.
|
||||
"""
|
||||
attrs = attrs or {}
|
||||
|
||||
# Set default attributes.
|
||||
snapshot_info = {
|
||||
"id": 'snapshot-id-' + uuid.uuid4().hex,
|
||||
"display_name": 'snapshot-name-' + uuid.uuid4().hex,
|
||||
"display_description": 'snapshot-description-' + uuid.uuid4().hex,
|
||||
"size": 10,
|
||||
"status": "available",
|
||||
"metadata": {"foo": "bar"},
|
||||
"created_at": "2015-06-03T18:49:19.000000",
|
||||
"volume_id": 'vloume-id-' + uuid.uuid4().hex,
|
||||
}
|
||||
|
||||
# Overwrite default attributes.
|
||||
snapshot_info.update(attrs)
|
||||
|
||||
snapshot_method = {'update': None}
|
||||
|
||||
snapshot = fakes.FakeResource(
|
||||
info=copy.deepcopy(snapshot_info),
|
||||
methods=copy.deepcopy(snapshot_method),
|
||||
loaded=True,
|
||||
)
|
||||
return snapshot
|
||||
|
||||
|
||||
def create_snapshots(attrs=None, count=2):
|
||||
"""Create multiple fake snapshots.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes
|
||||
:param int count:
|
||||
The number of snapshots to fake
|
||||
:return:
|
||||
A list of FakeResource objects faking the snapshots
|
||||
"""
|
||||
snapshots = []
|
||||
for i in range(0, count):
|
||||
snapshot = create_one_snapshot(attrs)
|
||||
snapshots.append(snapshot)
|
||||
|
||||
return snapshots
|
||||
|
||||
|
||||
def get_snapshots(snapshots=None, count=2):
|
||||
"""Get an iterable MagicMock object with a list of faked snapshots.
|
||||
|
||||
If snapshots list is provided, then initialize the Mock object with the
|
||||
list. Otherwise create one.
|
||||
|
||||
:param List volumes:
|
||||
A list of FakeResource objects faking snapshots
|
||||
:param Integer count:
|
||||
The number of snapshots to be faked
|
||||
:return
|
||||
An iterable Mock object with side_effect set to a list of faked
|
||||
snapshots
|
||||
"""
|
||||
if snapshots is None:
|
||||
snapshots = create_snapshots(count)
|
||||
|
||||
return mock.Mock(side_effect=snapshots)
|
||||
|
||||
|
||||
def create_one_backup(attrs=None):
|
||||
"""Create a fake backup.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes
|
||||
:return:
|
||||
A FakeResource object with id, name, volume_id, etc.
|
||||
"""
|
||||
attrs = attrs or {}
|
||||
|
||||
# Set default attributes.
|
||||
backup_info = {
|
||||
"id": 'backup-id-' + uuid.uuid4().hex,
|
||||
"name": 'backup-name-' + uuid.uuid4().hex,
|
||||
"volume_id": 'volume-id-' + uuid.uuid4().hex,
|
||||
"snapshot_id": 'snapshot-id' + uuid.uuid4().hex,
|
||||
"description": 'description-' + uuid.uuid4().hex,
|
||||
"object_count": None,
|
||||
"container": 'container-' + uuid.uuid4().hex,
|
||||
"size": random.randint(1, 20),
|
||||
"status": "error",
|
||||
"availability_zone": 'zone' + uuid.uuid4().hex,
|
||||
"links": 'links-' + uuid.uuid4().hex,
|
||||
}
|
||||
|
||||
# Overwrite default attributes.
|
||||
backup_info.update(attrs)
|
||||
|
||||
backup = fakes.FakeResource(info=copy.deepcopy(backup_info), loaded=True)
|
||||
return backup
|
||||
|
||||
|
||||
def create_backups(attrs=None, count=2):
|
||||
"""Create multiple fake backups.
|
||||
|
||||
:param Dictionary attrs:
|
||||
A dictionary with all attributes
|
||||
:param int count:
|
||||
The number of backups to fake
|
||||
:return:
|
||||
A list of FakeResource objects faking the backups
|
||||
"""
|
||||
backups = []
|
||||
for i in range(0, count):
|
||||
backup = create_one_backup(attrs)
|
||||
backups.append(backup)
|
||||
|
||||
return backups
|
||||
|
||||
|
||||
def get_backups(backups=None, count=2):
|
||||
"""Get an iterable MagicMock object with a list of faked backups.
|
||||
|
||||
If backups list is provided, then initialize the Mock object with the
|
||||
list. Otherwise create one.
|
||||
|
||||
:param List volumes:
|
||||
A list of FakeResource objects faking backups
|
||||
:param Integer count:
|
||||
The number of backups to be faked
|
||||
:return
|
||||
An iterable Mock object with side_effect set to a list of faked
|
||||
backups
|
||||
"""
|
||||
if backups is None:
|
||||
backups = create_backups(count)
|
||||
|
||||
return mock.Mock(side_effect=backups)
|
@ -1,471 +0,0 @@
|
||||
# Copyright 2015 iWeb Technologies Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
import copy
|
||||
from unittest import mock
|
||||
from unittest.mock import call
|
||||
|
||||
from osc_lib.cli import format_columns
|
||||
from osc_lib import exceptions
|
||||
from osc_lib import utils
|
||||
|
||||
from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes
|
||||
from openstackclient.volume.v1 import qos_specs
|
||||
|
||||
|
||||
class TestQos(volume_fakes.TestVolumev1):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.qos_mock = self.volume_client.qos_specs
|
||||
self.qos_mock.reset_mock()
|
||||
|
||||
self.types_mock = self.volume_client.volume_types
|
||||
self.types_mock.reset_mock()
|
||||
|
||||
|
||||
class TestQosAssociate(TestQos):
|
||||
volume_type = volume_fakes.create_one_volume_type()
|
||||
qos_spec = volume_fakes.create_one_qos()
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.qos_mock.get.return_value = self.qos_spec
|
||||
self.types_mock.get.return_value = self.volume_type
|
||||
# Get the command object to test
|
||||
self.cmd = qos_specs.AssociateQos(self.app, None)
|
||||
|
||||
def test_qos_associate(self):
|
||||
arglist = [self.qos_spec.id, self.volume_type.id]
|
||||
verifylist = [
|
||||
('qos_spec', self.qos_spec.id),
|
||||
('volume_type', self.volume_type.id),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.qos_mock.associate.assert_called_with(
|
||||
self.qos_spec.id, self.volume_type.id
|
||||
)
|
||||
self.assertIsNone(result)
|
||||
|
||||
|
||||
class TestQosCreate(TestQos):
|
||||
columns = ('consumer', 'id', 'name', 'properties')
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.new_qos_spec = volume_fakes.create_one_qos()
|
||||
self.datalist = (
|
||||
self.new_qos_spec.consumer,
|
||||
self.new_qos_spec.id,
|
||||
self.new_qos_spec.name,
|
||||
format_columns.DictColumn(self.new_qos_spec.specs),
|
||||
)
|
||||
self.qos_mock.create.return_value = self.new_qos_spec
|
||||
# Get the command object to test
|
||||
self.cmd = qos_specs.CreateQos(self.app, None)
|
||||
|
||||
def test_qos_create_without_properties(self):
|
||||
arglist = [
|
||||
self.new_qos_spec.name,
|
||||
]
|
||||
verifylist = [
|
||||
('name', self.new_qos_spec.name),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.qos_mock.create.assert_called_with(
|
||||
self.new_qos_spec.name, {'consumer': 'both'}
|
||||
)
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.datalist, data)
|
||||
|
||||
def test_qos_create_with_consumer(self):
|
||||
arglist = [
|
||||
'--consumer',
|
||||
self.new_qos_spec.consumer,
|
||||
self.new_qos_spec.name,
|
||||
]
|
||||
verifylist = [
|
||||
('consumer', self.new_qos_spec.consumer),
|
||||
('name', self.new_qos_spec.name),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.qos_mock.create.assert_called_with(
|
||||
self.new_qos_spec.name, {'consumer': self.new_qos_spec.consumer}
|
||||
)
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.datalist, data)
|
||||
|
||||
def test_qos_create_with_properties(self):
|
||||
arglist = [
|
||||
'--consumer',
|
||||
self.new_qos_spec.consumer,
|
||||
'--property',
|
||||
'foo=bar',
|
||||
'--property',
|
||||
'iops=9001',
|
||||
self.new_qos_spec.name,
|
||||
]
|
||||
verifylist = [
|
||||
('consumer', self.new_qos_spec.consumer),
|
||||
('property', self.new_qos_spec.specs),
|
||||
('name', self.new_qos_spec.name),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.new_qos_spec.specs.update(
|
||||
{'consumer': self.new_qos_spec.consumer}
|
||||
)
|
||||
self.qos_mock.create.assert_called_with(
|
||||
self.new_qos_spec.name, self.new_qos_spec.specs
|
||||
)
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.datalist, data)
|
||||
|
||||
|
||||
class TestQosDelete(TestQos):
|
||||
qos_specs = volume_fakes.create_qoses(count=2)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.qos_mock.get = volume_fakes.get_qoses(self.qos_specs)
|
||||
# Get the command object to test
|
||||
self.cmd = qos_specs.DeleteQos(self.app, None)
|
||||
|
||||
def test_qos_delete_with_id(self):
|
||||
arglist = [self.qos_specs[0].id]
|
||||
verifylist = [('qos_specs', [self.qos_specs[0].id])]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.qos_mock.delete.assert_called_with(self.qos_specs[0].id, False)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_qos_delete_with_name(self):
|
||||
arglist = [self.qos_specs[0].name]
|
||||
verifylist = [('qos_specs', [self.qos_specs[0].name])]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.qos_mock.delete.assert_called_with(self.qos_specs[0].id, False)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_qos_delete_with_force(self):
|
||||
arglist = ['--force', self.qos_specs[0].id]
|
||||
verifylist = [('force', True), ('qos_specs', [self.qos_specs[0].id])]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.qos_mock.delete.assert_called_with(self.qos_specs[0].id, True)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_delete_multiple_qoses(self):
|
||||
arglist = []
|
||||
for q in self.qos_specs:
|
||||
arglist.append(q.id)
|
||||
verifylist = [
|
||||
('qos_specs', arglist),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
calls = []
|
||||
for q in self.qos_specs:
|
||||
calls.append(call(q.id, False))
|
||||
self.qos_mock.delete.assert_has_calls(calls)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_delete_multiple_qoses_with_exception(self):
|
||||
arglist = [
|
||||
self.qos_specs[0].id,
|
||||
'unexist_qos',
|
||||
]
|
||||
verifylist = [
|
||||
('qos_specs', arglist),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
find_mock_result = [self.qos_specs[0], exceptions.CommandError]
|
||||
with mock.patch.object(
|
||||
utils, 'find_resource', side_effect=find_mock_result
|
||||
) as find_mock:
|
||||
try:
|
||||
self.cmd.take_action(parsed_args)
|
||||
self.fail('CommandError should be raised.')
|
||||
except exceptions.CommandError as e:
|
||||
self.assertEqual(
|
||||
'1 of 2 QoS specifications failed to delete.', str(e)
|
||||
)
|
||||
|
||||
find_mock.assert_any_call(self.qos_mock, self.qos_specs[0].id)
|
||||
find_mock.assert_any_call(self.qos_mock, 'unexist_qos')
|
||||
|
||||
self.assertEqual(2, find_mock.call_count)
|
||||
self.qos_mock.delete.assert_called_once_with(
|
||||
self.qos_specs[0].id, False
|
||||
)
|
||||
|
||||
|
||||
class TestQosDisassociate(TestQos):
|
||||
volume_type = volume_fakes.create_one_volume_type()
|
||||
qos_spec = volume_fakes.create_one_qos()
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.qos_mock.get.return_value = self.qos_spec
|
||||
self.types_mock.get.return_value = self.volume_type
|
||||
# Get the command object to test
|
||||
self.cmd = qos_specs.DisassociateQos(self.app, None)
|
||||
|
||||
def test_qos_disassociate_with_volume_type(self):
|
||||
arglist = [
|
||||
'--volume-type',
|
||||
self.volume_type.id,
|
||||
self.qos_spec.id,
|
||||
]
|
||||
verifylist = [
|
||||
('volume_type', self.volume_type.id),
|
||||
('qos_spec', self.qos_spec.id),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.qos_mock.disassociate.assert_called_with(
|
||||
self.qos_spec.id, self.volume_type.id
|
||||
)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_qos_disassociate_with_all_volume_types(self):
|
||||
arglist = [
|
||||
'--all',
|
||||
self.qos_spec.id,
|
||||
]
|
||||
verifylist = [('qos_spec', self.qos_spec.id)]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.qos_mock.disassociate_all.assert_called_with(self.qos_spec.id)
|
||||
self.assertIsNone(result)
|
||||
|
||||
|
||||
class TestQosList(TestQos):
|
||||
qos_specs = volume_fakes.create_qoses(count=2)
|
||||
qos_association = volume_fakes.create_one_qos_association()
|
||||
|
||||
columns = (
|
||||
'ID',
|
||||
'Name',
|
||||
'Consumer',
|
||||
'Associations',
|
||||
'Properties',
|
||||
)
|
||||
data = []
|
||||
for q in qos_specs:
|
||||
data.append(
|
||||
(
|
||||
q.id,
|
||||
q.name,
|
||||
q.consumer,
|
||||
format_columns.ListColumn([qos_association.name]),
|
||||
format_columns.DictColumn(q.specs),
|
||||
)
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.qos_mock.list.return_value = self.qos_specs
|
||||
self.qos_mock.get_associations.return_value = [self.qos_association]
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = qos_specs.ListQos(self.app, None)
|
||||
|
||||
def test_qos_list(self):
|
||||
arglist = []
|
||||
verifylist = []
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
self.qos_mock.list.assert_called_with()
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.data, list(data))
|
||||
|
||||
def test_qos_list_no_association(self):
|
||||
self.qos_mock.reset_mock()
|
||||
self.qos_mock.get_associations.side_effect = [
|
||||
[self.qos_association],
|
||||
exceptions.NotFound("NotFound"),
|
||||
]
|
||||
|
||||
arglist = []
|
||||
verifylist = []
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
self.qos_mock.list.assert_called_with()
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
|
||||
ex_data = copy.deepcopy(self.data)
|
||||
ex_data[1] = (
|
||||
self.qos_specs[1].id,
|
||||
self.qos_specs[1].name,
|
||||
self.qos_specs[1].consumer,
|
||||
format_columns.ListColumn(None),
|
||||
format_columns.DictColumn(self.qos_specs[1].specs),
|
||||
)
|
||||
self.assertCountEqual(ex_data, list(data))
|
||||
|
||||
|
||||
class TestQosSet(TestQos):
|
||||
qos_spec = volume_fakes.create_one_qos()
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.qos_mock.get.return_value = self.qos_spec
|
||||
# Get the command object to test
|
||||
self.cmd = qos_specs.SetQos(self.app, None)
|
||||
|
||||
def test_qos_set_with_properties_with_id(self):
|
||||
arglist = [
|
||||
'--no-property',
|
||||
'--property',
|
||||
'a=b',
|
||||
'--property',
|
||||
'c=d',
|
||||
self.qos_spec.id,
|
||||
]
|
||||
new_property = {"a": "b", "c": "d"}
|
||||
verifylist = [
|
||||
('no_property', True),
|
||||
('property', new_property),
|
||||
('qos_spec', self.qos_spec.id),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.qos_mock.unset_keys.assert_called_with(
|
||||
self.qos_spec.id,
|
||||
list(self.qos_spec.specs.keys()),
|
||||
)
|
||||
self.qos_mock.set_keys.assert_called_with(
|
||||
self.qos_spec.id,
|
||||
{"a": "b", "c": "d"},
|
||||
)
|
||||
self.assertIsNone(result)
|
||||
|
||||
|
||||
class TestQosShow(TestQos):
|
||||
qos_spec = volume_fakes.create_one_qos()
|
||||
qos_association = volume_fakes.create_one_qos_association()
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.qos_mock.get.return_value = self.qos_spec
|
||||
self.qos_mock.get_associations.return_value = [self.qos_association]
|
||||
# Get the command object to test
|
||||
self.cmd = qos_specs.ShowQos(self.app, None)
|
||||
|
||||
def test_qos_show(self):
|
||||
arglist = [self.qos_spec.id]
|
||||
verifylist = [('qos_spec', self.qos_spec.id)]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
self.qos_mock.get.assert_called_with(self.qos_spec.id)
|
||||
|
||||
collist = ('associations', 'consumer', 'id', 'name', 'properties')
|
||||
self.assertEqual(collist, columns)
|
||||
datalist = (
|
||||
format_columns.ListColumn([self.qos_association.name]),
|
||||
self.qos_spec.consumer,
|
||||
self.qos_spec.id,
|
||||
self.qos_spec.name,
|
||||
format_columns.DictColumn(self.qos_spec.specs),
|
||||
)
|
||||
self.assertCountEqual(datalist, tuple(data))
|
||||
|
||||
|
||||
class TestQosUnset(TestQos):
|
||||
qos_spec = volume_fakes.create_one_qos()
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.qos_mock.get.return_value = self.qos_spec
|
||||
# Get the command object to test
|
||||
self.cmd = qos_specs.UnsetQos(self.app, None)
|
||||
|
||||
def test_qos_unset_with_properties(self):
|
||||
arglist = [
|
||||
'--property',
|
||||
'iops',
|
||||
'--property',
|
||||
'foo',
|
||||
self.qos_spec.id,
|
||||
]
|
||||
verifylist = [
|
||||
('property', ['iops', 'foo']),
|
||||
('qos_spec', self.qos_spec.id),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.qos_mock.unset_keys.assert_called_with(
|
||||
self.qos_spec.id, ['iops', 'foo']
|
||||
)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_qos_unset_nothing(self):
|
||||
arglist = [
|
||||
self.qos_spec.id,
|
||||
]
|
||||
|
||||
verifylist = [
|
||||
('qos_spec', self.qos_spec.id),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
self.assertIsNone(result)
|
@ -1,295 +0,0 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
from osc_lib import exceptions
|
||||
|
||||
from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes
|
||||
from openstackclient.volume.v1 import service
|
||||
|
||||
|
||||
class TestService(volume_fakes.TestVolumev1):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
# Get a shortcut to the ServiceManager Mock
|
||||
self.service_mock = self.volume_client.services
|
||||
self.service_mock.reset_mock()
|
||||
|
||||
|
||||
class TestServiceList(TestService):
|
||||
# The service to be listed
|
||||
services = volume_fakes.create_one_service()
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.service_mock.list.return_value = [self.services]
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = service.ListService(self.app, None)
|
||||
|
||||
def test_service_list(self):
|
||||
arglist = [
|
||||
'--host',
|
||||
self.services.host,
|
||||
'--service',
|
||||
self.services.binary,
|
||||
]
|
||||
verifylist = [
|
||||
('host', self.services.host),
|
||||
('service', self.services.binary),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# In base command class Lister in cliff, abstract method take_action()
|
||||
# returns a tuple containing the column names and an iterable
|
||||
# containing the data to be listed.
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
expected_columns = [
|
||||
'Binary',
|
||||
'Host',
|
||||
'Zone',
|
||||
'Status',
|
||||
'State',
|
||||
'Updated At',
|
||||
]
|
||||
|
||||
# confirming if all expected columns are present in the result.
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
datalist = (
|
||||
(
|
||||
self.services.binary,
|
||||
self.services.host,
|
||||
self.services.zone,
|
||||
self.services.status,
|
||||
self.services.state,
|
||||
self.services.updated_at,
|
||||
),
|
||||
)
|
||||
|
||||
# confirming if all expected values are present in the result.
|
||||
self.assertEqual(datalist, tuple(data))
|
||||
|
||||
# checking if proper call was made to list services
|
||||
self.service_mock.list.assert_called_with(
|
||||
self.services.host,
|
||||
self.services.binary,
|
||||
)
|
||||
|
||||
# checking if prohibited columns are present in output
|
||||
self.assertNotIn("Disabled Reason", columns)
|
||||
self.assertNotIn(self.services.disabled_reason, tuple(data))
|
||||
|
||||
def test_service_list_with_long_option(self):
|
||||
arglist = [
|
||||
'--host',
|
||||
self.services.host,
|
||||
'--service',
|
||||
self.services.binary,
|
||||
'--long',
|
||||
]
|
||||
verifylist = [
|
||||
('host', self.services.host),
|
||||
('service', self.services.binary),
|
||||
('long', True),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# In base command class Lister in cliff, abstract method take_action()
|
||||
# returns a tuple containing the column names and an iterable
|
||||
# containing the data to be listed.
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
expected_columns = [
|
||||
'Binary',
|
||||
'Host',
|
||||
'Zone',
|
||||
'Status',
|
||||
'State',
|
||||
'Updated At',
|
||||
'Disabled Reason',
|
||||
]
|
||||
|
||||
# confirming if all expected columns are present in the result.
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
datalist = (
|
||||
(
|
||||
self.services.binary,
|
||||
self.services.host,
|
||||
self.services.zone,
|
||||
self.services.status,
|
||||
self.services.state,
|
||||
self.services.updated_at,
|
||||
self.services.disabled_reason,
|
||||
),
|
||||
)
|
||||
|
||||
# confirming if all expected values are present in the result.
|
||||
self.assertEqual(datalist, tuple(data))
|
||||
|
||||
self.service_mock.list.assert_called_with(
|
||||
self.services.host,
|
||||
self.services.binary,
|
||||
)
|
||||
|
||||
|
||||
class TestServiceSet(TestService):
|
||||
service = volume_fakes.create_one_service()
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.service_mock.enable.return_value = self.service
|
||||
self.service_mock.disable.return_value = self.service
|
||||
self.service_mock.disable_log_reason.return_value = self.service
|
||||
|
||||
self.cmd = service.SetService(self.app, None)
|
||||
|
||||
def test_service_set_nothing(self):
|
||||
arglist = [
|
||||
self.service.host,
|
||||
self.service.binary,
|
||||
]
|
||||
verifylist = [
|
||||
('host', self.service.host),
|
||||
('service', self.service.binary),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.service_mock.enable.assert_not_called()
|
||||
self.service_mock.disable.assert_not_called()
|
||||
self.service_mock.disable_log_reason.assert_not_called()
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_service_set_enable(self):
|
||||
arglist = [
|
||||
'--enable',
|
||||
self.service.host,
|
||||
self.service.binary,
|
||||
]
|
||||
verifylist = [
|
||||
('enable', True),
|
||||
('host', self.service.host),
|
||||
('service', self.service.binary),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.service_mock.enable.assert_called_with(
|
||||
self.service.host, self.service.binary
|
||||
)
|
||||
self.service_mock.disable.assert_not_called()
|
||||
self.service_mock.disable_log_reason.assert_not_called()
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_service_set_disable(self):
|
||||
arglist = [
|
||||
'--disable',
|
||||
self.service.host,
|
||||
self.service.binary,
|
||||
]
|
||||
verifylist = [
|
||||
('disable', True),
|
||||
('host', self.service.host),
|
||||
('service', self.service.binary),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.service_mock.disable.assert_called_with(
|
||||
self.service.host, self.service.binary
|
||||
)
|
||||
self.service_mock.enable.assert_not_called()
|
||||
self.service_mock.disable_log_reason.assert_not_called()
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_service_set_disable_with_reason(self):
|
||||
reason = 'earthquake'
|
||||
arglist = [
|
||||
'--disable',
|
||||
'--disable-reason',
|
||||
reason,
|
||||
self.service.host,
|
||||
self.service.binary,
|
||||
]
|
||||
verifylist = [
|
||||
('disable', True),
|
||||
('disable_reason', reason),
|
||||
('host', self.service.host),
|
||||
('service', self.service.binary),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.service_mock.disable_log_reason.assert_called_with(
|
||||
self.service.host, self.service.binary, reason
|
||||
)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_service_set_only_with_disable_reason(self):
|
||||
reason = 'earthquake'
|
||||
arglist = [
|
||||
'--disable-reason',
|
||||
reason,
|
||||
self.service.host,
|
||||
self.service.binary,
|
||||
]
|
||||
verifylist = [
|
||||
('disable_reason', reason),
|
||||
('host', self.service.host),
|
||||
('service', self.service.binary),
|
||||
]
|
||||
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(
|
||||
"Cannot specify option --disable-reason without "
|
||||
"--disable specified.",
|
||||
str(e),
|
||||
)
|
||||
|
||||
def test_service_set_enable_with_disable_reason(self):
|
||||
reason = 'earthquake'
|
||||
arglist = [
|
||||
'--enable',
|
||||
'--disable-reason',
|
||||
reason,
|
||||
self.service.host,
|
||||
self.service.binary,
|
||||
]
|
||||
verifylist = [
|
||||
('enable', True),
|
||||
('disable_reason', reason),
|
||||
('host', self.service.host),
|
||||
('service', self.service.binary),
|
||||
]
|
||||
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(
|
||||
"Cannot specify option --disable-reason without "
|
||||
"--disable specified.",
|
||||
str(e),
|
||||
)
|
@ -1,380 +0,0 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
from unittest import mock
|
||||
from unittest.mock import call
|
||||
|
||||
from osc_lib import exceptions
|
||||
from osc_lib import utils
|
||||
|
||||
from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes
|
||||
from openstackclient.volume.v1 import volume_transfer_request
|
||||
|
||||
|
||||
class TestTransfer(volume_fakes.TestVolumev1):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
# Get a shortcut to the TransferManager Mock
|
||||
self.transfer_mock = self.volume_client.transfers
|
||||
self.transfer_mock.reset_mock()
|
||||
|
||||
# Get a shortcut to the VolumeManager Mock
|
||||
self.volumes_mock = self.volume_client.volumes
|
||||
self.volumes_mock.reset_mock()
|
||||
|
||||
|
||||
class TestTransferAccept(TestTransfer):
|
||||
columns = (
|
||||
'id',
|
||||
'name',
|
||||
'volume_id',
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.volume_transfer = volume_fakes.create_one_transfer()
|
||||
self.data = (
|
||||
self.volume_transfer.id,
|
||||
self.volume_transfer.name,
|
||||
self.volume_transfer.volume_id,
|
||||
)
|
||||
|
||||
self.transfer_mock.get.return_value = self.volume_transfer
|
||||
self.transfer_mock.accept.return_value = self.volume_transfer
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = volume_transfer_request.AcceptTransferRequest(
|
||||
self.app, None
|
||||
)
|
||||
|
||||
def test_transfer_accept(self):
|
||||
arglist = [
|
||||
'--auth-key',
|
||||
'key_value',
|
||||
self.volume_transfer.id,
|
||||
]
|
||||
verifylist = [
|
||||
('transfer_request', self.volume_transfer.id),
|
||||
('auth_key', 'key_value'),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.transfer_mock.get.assert_called_once_with(
|
||||
self.volume_transfer.id,
|
||||
)
|
||||
self.transfer_mock.accept.assert_called_once_with(
|
||||
self.volume_transfer.id,
|
||||
'key_value',
|
||||
)
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertEqual(self.data, data)
|
||||
|
||||
def test_transfer_accept_no_option(self):
|
||||
arglist = [
|
||||
self.volume_transfer.id,
|
||||
]
|
||||
verifylist = [
|
||||
('transfer_request', self.volume_transfer.id),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.CommandError,
|
||||
self.cmd.take_action,
|
||||
parsed_args,
|
||||
)
|
||||
|
||||
|
||||
class TestTransferCreate(TestTransfer):
|
||||
volume = volume_fakes.create_one_volume()
|
||||
|
||||
columns = (
|
||||
'auth_key',
|
||||
'created_at',
|
||||
'id',
|
||||
'name',
|
||||
'volume_id',
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.volume_transfer = volume_fakes.create_one_transfer(
|
||||
attrs={
|
||||
'volume_id': self.volume.id,
|
||||
'auth_key': 'key',
|
||||
'created_at': 'time',
|
||||
},
|
||||
)
|
||||
self.data = (
|
||||
self.volume_transfer.auth_key,
|
||||
self.volume_transfer.created_at,
|
||||
self.volume_transfer.id,
|
||||
self.volume_transfer.name,
|
||||
self.volume_transfer.volume_id,
|
||||
)
|
||||
|
||||
self.transfer_mock.create.return_value = self.volume_transfer
|
||||
self.volumes_mock.get.return_value = self.volume
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = volume_transfer_request.CreateTransferRequest(
|
||||
self.app, None
|
||||
)
|
||||
|
||||
def test_transfer_create_without_name(self):
|
||||
arglist = [
|
||||
self.volume.id,
|
||||
]
|
||||
verifylist = [
|
||||
('volume', self.volume.id),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.transfer_mock.create.assert_called_once_with(self.volume.id, None)
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertEqual(self.data, data)
|
||||
|
||||
def test_transfer_create_with_name(self):
|
||||
arglist = [
|
||||
'--name',
|
||||
self.volume_transfer.name,
|
||||
self.volume.id,
|
||||
]
|
||||
verifylist = [
|
||||
('name', self.volume_transfer.name),
|
||||
('volume', self.volume.id),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.transfer_mock.create.assert_called_once_with(
|
||||
self.volume.id,
|
||||
self.volume_transfer.name,
|
||||
)
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertEqual(self.data, data)
|
||||
|
||||
|
||||
class TestTransferDelete(TestTransfer):
|
||||
volume_transfers = volume_fakes.create_transfers(count=2)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.transfer_mock.get = volume_fakes.get_transfers(
|
||||
self.volume_transfers,
|
||||
)
|
||||
self.transfer_mock.delete.return_value = None
|
||||
|
||||
# Get the command object to mock
|
||||
self.cmd = volume_transfer_request.DeleteTransferRequest(
|
||||
self.app, None
|
||||
)
|
||||
|
||||
def test_transfer_delete(self):
|
||||
arglist = [self.volume_transfers[0].id]
|
||||
verifylist = [("transfer_request", [self.volume_transfers[0].id])]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.transfer_mock.delete.assert_called_with(
|
||||
self.volume_transfers[0].id
|
||||
)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_delete_multiple_transfers(self):
|
||||
arglist = []
|
||||
for v in self.volume_transfers:
|
||||
arglist.append(v.id)
|
||||
verifylist = [
|
||||
('transfer_request', arglist),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
calls = []
|
||||
for v in self.volume_transfers:
|
||||
calls.append(call(v.id))
|
||||
self.transfer_mock.delete.assert_has_calls(calls)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_delete_multiple_transfers_with_exception(self):
|
||||
arglist = [
|
||||
self.volume_transfers[0].id,
|
||||
'unexist_transfer',
|
||||
]
|
||||
verifylist = [
|
||||
('transfer_request', arglist),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
find_mock_result = [self.volume_transfers[0], exceptions.CommandError]
|
||||
with mock.patch.object(
|
||||
utils, 'find_resource', side_effect=find_mock_result
|
||||
) as find_mock:
|
||||
try:
|
||||
self.cmd.take_action(parsed_args)
|
||||
self.fail('CommandError should be raised.')
|
||||
except exceptions.CommandError as e:
|
||||
self.assertEqual(
|
||||
'1 of 2 volume transfer requests failed to delete',
|
||||
str(e),
|
||||
)
|
||||
|
||||
find_mock.assert_any_call(
|
||||
self.transfer_mock, self.volume_transfers[0].id
|
||||
)
|
||||
find_mock.assert_any_call(self.transfer_mock, 'unexist_transfer')
|
||||
|
||||
self.assertEqual(2, find_mock.call_count)
|
||||
self.transfer_mock.delete.assert_called_once_with(
|
||||
self.volume_transfers[0].id,
|
||||
)
|
||||
|
||||
|
||||
class TestTransferList(TestTransfer):
|
||||
# The Transfers to be listed
|
||||
volume_transfers = volume_fakes.create_one_transfer()
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.transfer_mock.list.return_value = [self.volume_transfers]
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = volume_transfer_request.ListTransferRequest(self.app, None)
|
||||
|
||||
def test_transfer_list_without_argument(self):
|
||||
arglist = []
|
||||
verifylist = []
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# In base command class Lister in cliff, abstract method take_action()
|
||||
# returns a tuple containing the column names and an iterable
|
||||
# containing the data to be listed.
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
expected_columns = [
|
||||
'ID',
|
||||
'Name',
|
||||
'Volume',
|
||||
]
|
||||
|
||||
# confirming if all expected columns are present in the result.
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
datalist = (
|
||||
(
|
||||
self.volume_transfers.id,
|
||||
self.volume_transfers.name,
|
||||
self.volume_transfers.volume_id,
|
||||
),
|
||||
)
|
||||
|
||||
# confirming if all expected values are present in the result.
|
||||
self.assertEqual(datalist, tuple(data))
|
||||
|
||||
# checking if proper call was made to list volume_transfers
|
||||
self.transfer_mock.list.assert_called_with(
|
||||
detailed=True, search_opts={'all_tenants': 0}
|
||||
)
|
||||
|
||||
def test_transfer_list_with_argument(self):
|
||||
arglist = ["--all-projects"]
|
||||
verifylist = [("all_projects", True)]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# In base command class Lister in cliff, abstract method take_action()
|
||||
# returns a tuple containing the column names and an iterable
|
||||
# containing the data to be listed.
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
expected_columns = [
|
||||
'ID',
|
||||
'Name',
|
||||
'Volume',
|
||||
]
|
||||
|
||||
# confirming if all expected columns are present in the result.
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
datalist = (
|
||||
(
|
||||
self.volume_transfers.id,
|
||||
self.volume_transfers.name,
|
||||
self.volume_transfers.volume_id,
|
||||
),
|
||||
)
|
||||
|
||||
# confirming if all expected values are present in the result.
|
||||
self.assertEqual(datalist, tuple(data))
|
||||
|
||||
# checking if proper call was made to list volume_transfers
|
||||
self.transfer_mock.list.assert_called_with(
|
||||
detailed=True, search_opts={'all_tenants': 1}
|
||||
)
|
||||
|
||||
|
||||
class TestTransferShow(TestTransfer):
|
||||
columns = (
|
||||
'created_at',
|
||||
'id',
|
||||
'name',
|
||||
'volume_id',
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.volume_transfer = volume_fakes.create_one_transfer(
|
||||
attrs={'created_at': 'time'}
|
||||
)
|
||||
self.data = (
|
||||
self.volume_transfer.created_at,
|
||||
self.volume_transfer.id,
|
||||
self.volume_transfer.name,
|
||||
self.volume_transfer.volume_id,
|
||||
)
|
||||
|
||||
self.transfer_mock.get.return_value = self.volume_transfer
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = volume_transfer_request.ShowTransferRequest(self.app, None)
|
||||
|
||||
def test_transfer_show(self):
|
||||
arglist = [
|
||||
self.volume_transfer.id,
|
||||
]
|
||||
verifylist = [
|
||||
('transfer_request', self.volume_transfer.id),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.transfer_mock.get.assert_called_once_with(self.volume_transfer.id)
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertEqual(self.data, data)
|
@ -1,633 +0,0 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
from unittest import mock
|
||||
from unittest.mock import call
|
||||
|
||||
from osc_lib.cli import format_columns
|
||||
from osc_lib import exceptions
|
||||
from osc_lib import utils
|
||||
|
||||
from openstackclient.tests.unit import utils as tests_utils
|
||||
from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes
|
||||
from openstackclient.volume.v1 import volume_type
|
||||
|
||||
|
||||
class TestType(volume_fakes.TestVolumev1):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.types_mock = self.volume_client.volume_types
|
||||
self.types_mock.reset_mock()
|
||||
|
||||
self.encryption_types_mock = self.volume_client.volume_encryption_types
|
||||
self.encryption_types_mock.reset_mock()
|
||||
|
||||
|
||||
class TestTypeCreate(TestType):
|
||||
columns = (
|
||||
'description',
|
||||
'id',
|
||||
'is_public',
|
||||
'name',
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.new_volume_type = volume_fakes.create_one_volume_type(
|
||||
methods={'set_keys': {'myprop': 'myvalue'}},
|
||||
)
|
||||
self.data = (
|
||||
self.new_volume_type.description,
|
||||
self.new_volume_type.id,
|
||||
True,
|
||||
self.new_volume_type.name,
|
||||
)
|
||||
|
||||
self.types_mock.create.return_value = self.new_volume_type
|
||||
# Get the command object to test
|
||||
self.cmd = volume_type.CreateVolumeType(self.app, None)
|
||||
|
||||
def test_type_create(self):
|
||||
arglist = [
|
||||
self.new_volume_type.name,
|
||||
]
|
||||
verifylist = [
|
||||
("name", self.new_volume_type.name),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
self.types_mock.create.assert_called_with(
|
||||
self.new_volume_type.name,
|
||||
)
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.data, data)
|
||||
|
||||
def test_type_create_with_encryption(self):
|
||||
encryption_info = {
|
||||
'provider': 'LuksEncryptor',
|
||||
'cipher': 'aes-xts-plain64',
|
||||
'key_size': '128',
|
||||
'control_location': 'front-end',
|
||||
}
|
||||
encryption_type = volume_fakes.create_one_encryption_volume_type(
|
||||
attrs=encryption_info,
|
||||
)
|
||||
self.new_volume_type = volume_fakes.create_one_volume_type(
|
||||
attrs={'encryption': encryption_info},
|
||||
)
|
||||
self.types_mock.create.return_value = self.new_volume_type
|
||||
self.encryption_types_mock.create.return_value = encryption_type
|
||||
encryption_columns = (
|
||||
'description',
|
||||
'encryption',
|
||||
'id',
|
||||
'is_public',
|
||||
'name',
|
||||
)
|
||||
encryption_data = (
|
||||
self.new_volume_type.description,
|
||||
format_columns.DictColumn(encryption_info),
|
||||
self.new_volume_type.id,
|
||||
True,
|
||||
self.new_volume_type.name,
|
||||
)
|
||||
arglist = [
|
||||
'--encryption-provider',
|
||||
'LuksEncryptor',
|
||||
'--encryption-cipher',
|
||||
'aes-xts-plain64',
|
||||
'--encryption-key-size',
|
||||
'128',
|
||||
'--encryption-control-location',
|
||||
'front-end',
|
||||
self.new_volume_type.name,
|
||||
]
|
||||
verifylist = [
|
||||
('encryption_provider', 'LuksEncryptor'),
|
||||
('encryption_cipher', 'aes-xts-plain64'),
|
||||
('encryption_key_size', 128),
|
||||
('encryption_control_location', 'front-end'),
|
||||
('name', self.new_volume_type.name),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
self.types_mock.create.assert_called_with(
|
||||
self.new_volume_type.name,
|
||||
)
|
||||
body = {
|
||||
'provider': 'LuksEncryptor',
|
||||
'cipher': 'aes-xts-plain64',
|
||||
'key_size': 128,
|
||||
'control_location': 'front-end',
|
||||
}
|
||||
self.encryption_types_mock.create.assert_called_with(
|
||||
self.new_volume_type,
|
||||
body,
|
||||
)
|
||||
self.assertEqual(encryption_columns, columns)
|
||||
self.assertCountEqual(encryption_data, data)
|
||||
|
||||
|
||||
class TestTypeDelete(TestType):
|
||||
volume_types = volume_fakes.create_volume_types(count=2)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.types_mock.get = volume_fakes.get_volume_types(self.volume_types)
|
||||
self.types_mock.delete.return_value = None
|
||||
|
||||
# Get the command object to mock
|
||||
self.cmd = volume_type.DeleteVolumeType(self.app, None)
|
||||
|
||||
def test_type_delete(self):
|
||||
arglist = [self.volume_types[0].id]
|
||||
verifylist = [("volume_types", [self.volume_types[0].id])]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.types_mock.delete.assert_called_with(self.volume_types[0])
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_delete_multiple_types(self):
|
||||
arglist = []
|
||||
for t in self.volume_types:
|
||||
arglist.append(t.id)
|
||||
verifylist = [
|
||||
('volume_types', arglist),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
calls = []
|
||||
for t in self.volume_types:
|
||||
calls.append(call(t))
|
||||
self.types_mock.delete.assert_has_calls(calls)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_delete_multiple_types_with_exception(self):
|
||||
arglist = [
|
||||
self.volume_types[0].id,
|
||||
'unexist_type',
|
||||
]
|
||||
verifylist = [
|
||||
('volume_types', arglist),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
find_mock_result = [self.volume_types[0], exceptions.CommandError]
|
||||
with mock.patch.object(
|
||||
utils, 'find_resource', side_effect=find_mock_result
|
||||
) as find_mock:
|
||||
try:
|
||||
self.cmd.take_action(parsed_args)
|
||||
self.fail('CommandError should be raised.')
|
||||
except exceptions.CommandError as e:
|
||||
self.assertEqual(
|
||||
'1 of 2 volume types failed to delete.', str(e)
|
||||
)
|
||||
|
||||
find_mock.assert_any_call(self.types_mock, self.volume_types[0].id)
|
||||
find_mock.assert_any_call(self.types_mock, 'unexist_type')
|
||||
|
||||
self.assertEqual(2, find_mock.call_count)
|
||||
self.types_mock.delete.assert_called_once_with(
|
||||
self.volume_types[0]
|
||||
)
|
||||
|
||||
|
||||
class TestTypeList(TestType):
|
||||
volume_types = volume_fakes.create_volume_types()
|
||||
|
||||
columns = [
|
||||
"ID",
|
||||
"Name",
|
||||
"Is Public",
|
||||
]
|
||||
columns_long = ["ID", "Name", "Is Public", "Properties"]
|
||||
|
||||
data = []
|
||||
for t in volume_types:
|
||||
data.append(
|
||||
(
|
||||
t.id,
|
||||
t.name,
|
||||
t.is_public,
|
||||
)
|
||||
)
|
||||
data_long = []
|
||||
for t in volume_types:
|
||||
data_long.append(
|
||||
(
|
||||
t.id,
|
||||
t.name,
|
||||
t.is_public,
|
||||
format_columns.DictColumn(t.extra_specs),
|
||||
)
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.types_mock.list.return_value = self.volume_types
|
||||
self.encryption_types_mock.create.return_value = None
|
||||
self.encryption_types_mock.update.return_value = None
|
||||
# get the command to test
|
||||
self.cmd = volume_type.ListVolumeType(self.app, None)
|
||||
|
||||
def test_type_list_without_options(self):
|
||||
arglist = []
|
||||
verifylist = [
|
||||
("long", False),
|
||||
("encryption_type", False),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
self.types_mock.list.assert_called_once_with()
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.data, list(data))
|
||||
|
||||
def test_type_list_with_options(self):
|
||||
arglist = [
|
||||
"--long",
|
||||
]
|
||||
verifylist = [
|
||||
("long", True),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
self.types_mock.list.assert_called_once_with()
|
||||
self.assertEqual(self.columns_long, columns)
|
||||
self.assertCountEqual(self.data_long, list(data))
|
||||
|
||||
def test_type_list_with_encryption(self):
|
||||
encryption_type = volume_fakes.create_one_encryption_volume_type(
|
||||
attrs={'volume_type_id': self.volume_types[0].id},
|
||||
)
|
||||
encryption_info = {
|
||||
'provider': 'LuksEncryptor',
|
||||
'cipher': None,
|
||||
'key_size': None,
|
||||
'control_location': 'front-end',
|
||||
}
|
||||
encryption_columns = self.columns + [
|
||||
"Encryption",
|
||||
]
|
||||
encryption_data = []
|
||||
encryption_data.append(
|
||||
(
|
||||
self.volume_types[0].id,
|
||||
self.volume_types[0].name,
|
||||
self.volume_types[0].is_public,
|
||||
volume_type.EncryptionInfoColumn(
|
||||
self.volume_types[0].id,
|
||||
{self.volume_types[0].id: encryption_info},
|
||||
),
|
||||
)
|
||||
)
|
||||
encryption_data.append(
|
||||
(
|
||||
self.volume_types[1].id,
|
||||
self.volume_types[1].name,
|
||||
self.volume_types[1].is_public,
|
||||
volume_type.EncryptionInfoColumn(self.volume_types[1].id, {}),
|
||||
)
|
||||
)
|
||||
|
||||
self.encryption_types_mock.list.return_value = [encryption_type]
|
||||
arglist = [
|
||||
"--encryption-type",
|
||||
]
|
||||
verifylist = [
|
||||
("encryption_type", True),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
self.encryption_types_mock.list.assert_called_once_with()
|
||||
self.types_mock.list.assert_called_once_with()
|
||||
self.assertEqual(encryption_columns, columns)
|
||||
self.assertCountEqual(encryption_data, list(data))
|
||||
|
||||
|
||||
class TestTypeSet(TestType):
|
||||
volume_type = volume_fakes.create_one_volume_type(
|
||||
methods={'set_keys': None},
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.types_mock.get.return_value = self.volume_type
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = volume_type.SetVolumeType(self.app, None)
|
||||
|
||||
def test_type_set_nothing(self):
|
||||
arglist = [
|
||||
self.volume_type.id,
|
||||
]
|
||||
verifylist = [
|
||||
('volume_type', self.volume_type.id),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_type_set_property(self):
|
||||
arglist = [
|
||||
'--property',
|
||||
'myprop=myvalue',
|
||||
self.volume_type.id,
|
||||
]
|
||||
verifylist = [
|
||||
('property', {'myprop': 'myvalue'}),
|
||||
('volume_type', self.volume_type.id),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
self.volume_type.set_keys.assert_called_once_with(
|
||||
{'myprop': 'myvalue'}
|
||||
)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_type_set_new_encryption(self):
|
||||
arglist = [
|
||||
'--encryption-provider',
|
||||
'LuksEncryptor',
|
||||
'--encryption-cipher',
|
||||
'aes-xts-plain64',
|
||||
'--encryption-key-size',
|
||||
'128',
|
||||
'--encryption-control-location',
|
||||
'front-end',
|
||||
self.volume_type.id,
|
||||
]
|
||||
verifylist = [
|
||||
('encryption_provider', 'LuksEncryptor'),
|
||||
('encryption_cipher', 'aes-xts-plain64'),
|
||||
('encryption_key_size', 128),
|
||||
('encryption_control_location', 'front-end'),
|
||||
('volume_type', self.volume_type.id),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
body = {
|
||||
'provider': 'LuksEncryptor',
|
||||
'cipher': 'aes-xts-plain64',
|
||||
'key_size': 128,
|
||||
'control_location': 'front-end',
|
||||
}
|
||||
self.encryption_types_mock.create.assert_called_with(
|
||||
self.volume_type,
|
||||
body,
|
||||
)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_type_set_new_encryption_without_provider(self):
|
||||
arglist = [
|
||||
'--encryption-cipher',
|
||||
'aes-xts-plain64',
|
||||
'--encryption-key-size',
|
||||
'128',
|
||||
'--encryption-control-location',
|
||||
'front-end',
|
||||
self.volume_type.id,
|
||||
]
|
||||
verifylist = [
|
||||
('encryption_cipher', 'aes-xts-plain64'),
|
||||
('encryption_key_size', 128),
|
||||
('encryption_control_location', 'front-end'),
|
||||
('volume_type', self.volume_type.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(
|
||||
"Command Failed: One or more of the operations failed",
|
||||
str(e),
|
||||
)
|
||||
self.encryption_types_mock.create.assert_not_called()
|
||||
self.encryption_types_mock.update.assert_not_called()
|
||||
|
||||
|
||||
class TestTypeShow(TestType):
|
||||
columns = (
|
||||
'description',
|
||||
'id',
|
||||
'is_public',
|
||||
'name',
|
||||
'properties',
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.volume_type = volume_fakes.create_one_volume_type()
|
||||
self.data = (
|
||||
self.volume_type.description,
|
||||
self.volume_type.id,
|
||||
True,
|
||||
self.volume_type.name,
|
||||
format_columns.DictColumn(self.volume_type.extra_specs),
|
||||
)
|
||||
|
||||
self.types_mock.get.return_value = self.volume_type
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = volume_type.ShowVolumeType(self.app, None)
|
||||
|
||||
def test_type_show(self):
|
||||
arglist = [self.volume_type.id]
|
||||
verifylist = [
|
||||
("volume_type", self.volume_type.id),
|
||||
("encryption_type", False),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
self.types_mock.get.assert_called_with(self.volume_type.id)
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.data, data)
|
||||
|
||||
def test_type_show_with_encryption(self):
|
||||
encryption_type = volume_fakes.create_one_encryption_volume_type()
|
||||
encryption_info = {
|
||||
'provider': 'LuksEncryptor',
|
||||
'cipher': None,
|
||||
'key_size': None,
|
||||
'control_location': 'front-end',
|
||||
}
|
||||
self.volume_type = volume_fakes.create_one_volume_type(
|
||||
attrs={'encryption': encryption_info},
|
||||
)
|
||||
self.types_mock.get.return_value = self.volume_type
|
||||
self.encryption_types_mock.get.return_value = encryption_type
|
||||
encryption_columns = (
|
||||
'description',
|
||||
'encryption',
|
||||
'id',
|
||||
'is_public',
|
||||
'name',
|
||||
'properties',
|
||||
)
|
||||
encryption_data = (
|
||||
self.volume_type.description,
|
||||
format_columns.DictColumn(encryption_info),
|
||||
self.volume_type.id,
|
||||
True,
|
||||
self.volume_type.name,
|
||||
format_columns.DictColumn(self.volume_type.extra_specs),
|
||||
)
|
||||
arglist = ['--encryption-type', self.volume_type.id]
|
||||
verifylist = [
|
||||
('encryption_type', True),
|
||||
("volume_type", self.volume_type.id),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
self.types_mock.get.assert_called_with(self.volume_type.id)
|
||||
self.encryption_types_mock.get.assert_called_with(self.volume_type.id)
|
||||
self.assertEqual(encryption_columns, columns)
|
||||
self.assertCountEqual(encryption_data, data)
|
||||
|
||||
|
||||
class TestTypeUnset(TestType):
|
||||
volume_type = volume_fakes.create_one_volume_type(
|
||||
methods={'unset_keys': None},
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.types_mock.get.return_value = self.volume_type
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = volume_type.UnsetVolumeType(self.app, None)
|
||||
|
||||
def test_type_unset_property(self):
|
||||
arglist = [
|
||||
'--property',
|
||||
'property',
|
||||
'--property',
|
||||
'multi_property',
|
||||
self.volume_type.id,
|
||||
]
|
||||
verifylist = [
|
||||
('encryption_type', False),
|
||||
('property', ['property', 'multi_property']),
|
||||
('volume_type', self.volume_type.id),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
self.volume_type.unset_keys.assert_called_once_with(
|
||||
['property', 'multi_property']
|
||||
)
|
||||
self.encryption_types_mock.delete.assert_not_called()
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_type_unset_failed_with_missing_volume_type_argument(self):
|
||||
arglist = [
|
||||
'--property',
|
||||
'property',
|
||||
'--property',
|
||||
'multi_property',
|
||||
]
|
||||
verifylist = [
|
||||
('property', ['property', 'multi_property']),
|
||||
]
|
||||
|
||||
self.assertRaises(
|
||||
tests_utils.ParserException,
|
||||
self.check_parser,
|
||||
self.cmd,
|
||||
arglist,
|
||||
verifylist,
|
||||
)
|
||||
|
||||
def test_type_unset_nothing(self):
|
||||
arglist = [
|
||||
self.volume_type.id,
|
||||
]
|
||||
verifylist = [
|
||||
('volume_type', self.volume_type.id),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_type_unset_encryption_type(self):
|
||||
arglist = [
|
||||
'--encryption-type',
|
||||
self.volume_type.id,
|
||||
]
|
||||
verifylist = [
|
||||
('encryption_type', True),
|
||||
('volume_type', self.volume_type.id),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
self.encryption_types_mock.delete.assert_called_with(self.volume_type)
|
||||
self.assertIsNone(result)
|
||||
|
||||
|
||||
class TestColumns(TestType):
|
||||
def test_encryption_info_column_with_info(self):
|
||||
fake_volume_type = volume_fakes.create_one_volume_type()
|
||||
type_id = fake_volume_type.id
|
||||
|
||||
encryption_info = {
|
||||
'provider': 'LuksEncryptor',
|
||||
'cipher': None,
|
||||
'key_size': None,
|
||||
'control_location': 'front-end',
|
||||
}
|
||||
col = volume_type.EncryptionInfoColumn(
|
||||
type_id, {type_id: encryption_info}
|
||||
)
|
||||
self.assertEqual(
|
||||
utils.format_dict(encryption_info), col.human_readable()
|
||||
)
|
||||
self.assertEqual(encryption_info, col.machine_readable())
|
||||
|
||||
def test_encryption_info_column_without_info(self):
|
||||
fake_volume_type = volume_fakes.create_one_volume_type()
|
||||
type_id = fake_volume_type.id
|
||||
|
||||
col = volume_type.EncryptionInfoColumn(type_id, {})
|
||||
self.assertEqual('-', col.human_readable())
|
||||
self.assertIsNone(col.machine_readable())
|
File diff suppressed because it is too large
Load Diff
@ -1,435 +0,0 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
from unittest import mock
|
||||
from unittest.mock import call
|
||||
|
||||
from osc_lib import exceptions
|
||||
from osc_lib import utils
|
||||
|
||||
from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes
|
||||
from openstackclient.volume.v1 import volume_backup
|
||||
|
||||
|
||||
class TestBackup(volume_fakes.TestVolumev1):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.backups_mock = self.volume_client.backups
|
||||
self.backups_mock.reset_mock()
|
||||
self.volumes_mock = self.volume_client.volumes
|
||||
self.volumes_mock.reset_mock()
|
||||
self.snapshots_mock = self.volume_client.volume_snapshots
|
||||
self.snapshots_mock.reset_mock()
|
||||
self.restores_mock = self.volume_client.restores
|
||||
self.restores_mock.reset_mock()
|
||||
|
||||
|
||||
class TestBackupCreate(TestBackup):
|
||||
volume = volume_fakes.create_one_volume()
|
||||
|
||||
columns = (
|
||||
'availability_zone',
|
||||
'container',
|
||||
'description',
|
||||
'id',
|
||||
'name',
|
||||
'object_count',
|
||||
'size',
|
||||
'snapshot_id',
|
||||
'status',
|
||||
'volume_id',
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.new_backup = volume_fakes.create_one_backup(
|
||||
attrs={'volume_id': self.volume.id},
|
||||
)
|
||||
self.data = (
|
||||
self.new_backup.availability_zone,
|
||||
self.new_backup.container,
|
||||
self.new_backup.description,
|
||||
self.new_backup.id,
|
||||
self.new_backup.name,
|
||||
self.new_backup.object_count,
|
||||
self.new_backup.size,
|
||||
self.new_backup.snapshot_id,
|
||||
self.new_backup.status,
|
||||
self.new_backup.volume_id,
|
||||
)
|
||||
self.volumes_mock.get.return_value = self.volume
|
||||
self.backups_mock.create.return_value = self.new_backup
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = volume_backup.CreateVolumeBackup(self.app, None)
|
||||
|
||||
def test_backup_create(self):
|
||||
arglist = [
|
||||
"--name",
|
||||
self.new_backup.name,
|
||||
"--description",
|
||||
self.new_backup.description,
|
||||
"--container",
|
||||
self.new_backup.container,
|
||||
self.new_backup.volume_id,
|
||||
]
|
||||
verifylist = [
|
||||
("name", self.new_backup.name),
|
||||
("description", self.new_backup.description),
|
||||
("container", self.new_backup.container),
|
||||
("volume", self.new_backup.volume_id),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.backups_mock.create.assert_called_with(
|
||||
self.new_backup.volume_id,
|
||||
self.new_backup.container,
|
||||
self.new_backup.name,
|
||||
self.new_backup.description,
|
||||
)
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.data, data)
|
||||
|
||||
def test_backup_create_without_name(self):
|
||||
arglist = [
|
||||
"--description",
|
||||
self.new_backup.description,
|
||||
"--container",
|
||||
self.new_backup.container,
|
||||
self.new_backup.volume_id,
|
||||
]
|
||||
verifylist = [
|
||||
("description", self.new_backup.description),
|
||||
("container", self.new_backup.container),
|
||||
("volume", self.new_backup.volume_id),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.backups_mock.create.assert_called_with(
|
||||
self.new_backup.volume_id,
|
||||
self.new_backup.container,
|
||||
None,
|
||||
self.new_backup.description,
|
||||
)
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.data, data)
|
||||
|
||||
|
||||
class TestBackupDelete(TestBackup):
|
||||
backups = volume_fakes.create_backups(count=2)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.backups_mock.get = volume_fakes.get_backups(self.backups)
|
||||
self.backups_mock.delete.return_value = None
|
||||
|
||||
# Get the command object to mock
|
||||
self.cmd = volume_backup.DeleteVolumeBackup(self.app, None)
|
||||
|
||||
def test_backup_delete(self):
|
||||
arglist = [self.backups[0].id]
|
||||
verifylist = [("backups", [self.backups[0].id])]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.backups_mock.delete.assert_called_with(self.backups[0].id)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_delete_multiple_backups(self):
|
||||
arglist = []
|
||||
for b in self.backups:
|
||||
arglist.append(b.id)
|
||||
verifylist = [
|
||||
('backups', arglist),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
calls = []
|
||||
for b in self.backups:
|
||||
calls.append(call(b.id))
|
||||
self.backups_mock.delete.assert_has_calls(calls)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_delete_multiple_backups_with_exception(self):
|
||||
arglist = [
|
||||
self.backups[0].id,
|
||||
'unexist_backup',
|
||||
]
|
||||
verifylist = [
|
||||
('backups', arglist),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
find_mock_result = [self.backups[0], exceptions.CommandError]
|
||||
with mock.patch.object(
|
||||
utils, 'find_resource', side_effect=find_mock_result
|
||||
) as find_mock:
|
||||
try:
|
||||
self.cmd.take_action(parsed_args)
|
||||
self.fail('CommandError should be raised.')
|
||||
except exceptions.CommandError as e:
|
||||
self.assertEqual('1 of 2 backups failed to delete.', str(e))
|
||||
|
||||
find_mock.assert_any_call(self.backups_mock, self.backups[0].id)
|
||||
find_mock.assert_any_call(self.backups_mock, 'unexist_backup')
|
||||
|
||||
self.assertEqual(2, find_mock.call_count)
|
||||
self.backups_mock.delete.assert_called_once_with(
|
||||
self.backups[0].id,
|
||||
)
|
||||
|
||||
|
||||
class TestBackupList(TestBackup):
|
||||
volume = volume_fakes.create_one_volume()
|
||||
backups = volume_fakes.create_backups(
|
||||
attrs={'volume_id': volume.display_name},
|
||||
count=3,
|
||||
)
|
||||
|
||||
columns = [
|
||||
'ID',
|
||||
'Name',
|
||||
'Description',
|
||||
'Status',
|
||||
'Size',
|
||||
]
|
||||
columns_long = columns + [
|
||||
'Availability Zone',
|
||||
'Volume',
|
||||
'Container',
|
||||
]
|
||||
|
||||
data = []
|
||||
for b in backups:
|
||||
data.append(
|
||||
(
|
||||
b.id,
|
||||
b.name,
|
||||
b.description,
|
||||
b.status,
|
||||
b.size,
|
||||
)
|
||||
)
|
||||
data_long = []
|
||||
for b in backups:
|
||||
data_long.append(
|
||||
(
|
||||
b.id,
|
||||
b.name,
|
||||
b.description,
|
||||
b.status,
|
||||
b.size,
|
||||
b.availability_zone,
|
||||
volume_backup.VolumeIdColumn(b.volume_id),
|
||||
b.container,
|
||||
)
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.volumes_mock.list.return_value = [self.volume]
|
||||
self.backups_mock.list.return_value = self.backups
|
||||
self.volumes_mock.get.return_value = self.volume
|
||||
# Get the command to test
|
||||
self.cmd = volume_backup.ListVolumeBackup(self.app, None)
|
||||
|
||||
def test_backup_list_without_options(self):
|
||||
arglist = []
|
||||
verifylist = [
|
||||
("long", False),
|
||||
("name", None),
|
||||
("status", None),
|
||||
("volume", None),
|
||||
('all_projects', False),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
search_opts = {
|
||||
"name": None,
|
||||
"status": None,
|
||||
"volume_id": None,
|
||||
"all_tenants": False,
|
||||
}
|
||||
self.volumes_mock.get.assert_not_called()
|
||||
self.backups_mock.list.assert_called_with(
|
||||
search_opts=search_opts,
|
||||
)
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.data, list(data))
|
||||
|
||||
def test_backup_list_with_options(self):
|
||||
arglist = [
|
||||
"--long",
|
||||
"--name",
|
||||
self.backups[0].name,
|
||||
"--status",
|
||||
"error",
|
||||
"--volume",
|
||||
self.volume.id,
|
||||
"--all-projects",
|
||||
]
|
||||
verifylist = [
|
||||
("long", True),
|
||||
("name", self.backups[0].name),
|
||||
("status", "error"),
|
||||
("volume", self.volume.id),
|
||||
('all_projects', True),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
search_opts = {
|
||||
"name": self.backups[0].name,
|
||||
"status": "error",
|
||||
"volume_id": self.volume.id,
|
||||
"all_tenants": True,
|
||||
}
|
||||
self.volumes_mock.get.assert_called_once_with(self.volume.id)
|
||||
self.backups_mock.list.assert_called_with(
|
||||
search_opts=search_opts,
|
||||
)
|
||||
self.assertEqual(self.columns_long, columns)
|
||||
self.assertCountEqual(self.data_long, list(data))
|
||||
|
||||
|
||||
class TestBackupRestore(TestBackup):
|
||||
volume = volume_fakes.create_one_volume()
|
||||
backup = volume_fakes.create_one_backup(
|
||||
attrs={'volume_id': volume.id},
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.backups_mock.get.return_value = self.backup
|
||||
self.volumes_mock.get.return_value = self.volume
|
||||
self.restores_mock.restore.return_value = (
|
||||
volume_fakes.create_one_volume(
|
||||
{'id': self.volume['id']},
|
||||
)
|
||||
)
|
||||
# Get the command object to mock
|
||||
self.cmd = volume_backup.RestoreVolumeBackup(self.app, None)
|
||||
|
||||
def test_backup_restore(self):
|
||||
arglist = [
|
||||
self.backup.id,
|
||||
]
|
||||
verifylist = [
|
||||
("backup", self.backup.id),
|
||||
("volume", None),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
self.restores_mock.restore.assert_called_with(self.backup.id, None)
|
||||
self.assertIsNotNone(result)
|
||||
|
||||
def test_backup_restore_with_existing_volume(self):
|
||||
arglist = [
|
||||
self.backup.id,
|
||||
self.backup.volume_id,
|
||||
]
|
||||
verifylist = [
|
||||
("backup", self.backup.id),
|
||||
("volume", self.backup.volume_id),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
self.restores_mock.restore.assert_called_with(
|
||||
self.backup.id,
|
||||
self.backup.volume_id,
|
||||
)
|
||||
self.assertIsNotNone(result)
|
||||
|
||||
def test_backup_restore_with_invalid_volume(self):
|
||||
arglist = [
|
||||
self.backup.id,
|
||||
"unexist_volume",
|
||||
]
|
||||
verifylist = [
|
||||
("backup", self.backup.id),
|
||||
("volume", "unexist_volume"),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
with mock.patch.object(
|
||||
utils,
|
||||
'find_resource',
|
||||
side_effect=exceptions.CommandError(),
|
||||
):
|
||||
self.assertRaises(
|
||||
exceptions.CommandError,
|
||||
self.cmd.take_action,
|
||||
parsed_args,
|
||||
)
|
||||
|
||||
|
||||
class TestBackupShow(TestBackup):
|
||||
columns = (
|
||||
'availability_zone',
|
||||
'container',
|
||||
'description',
|
||||
'id',
|
||||
'name',
|
||||
'object_count',
|
||||
'size',
|
||||
'snapshot_id',
|
||||
'status',
|
||||
'volume_id',
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.backup = volume_fakes.create_one_backup()
|
||||
self.data = (
|
||||
self.backup.availability_zone,
|
||||
self.backup.container,
|
||||
self.backup.description,
|
||||
self.backup.id,
|
||||
self.backup.name,
|
||||
self.backup.object_count,
|
||||
self.backup.size,
|
||||
self.backup.snapshot_id,
|
||||
self.backup.status,
|
||||
self.backup.volume_id,
|
||||
)
|
||||
self.backups_mock.get.return_value = self.backup
|
||||
# Get the command object to test
|
||||
self.cmd = volume_backup.ShowVolumeBackup(self.app, None)
|
||||
|
||||
def test_backup_show(self):
|
||||
arglist = [self.backup.id]
|
||||
verifylist = [("backup", self.backup.id)]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
self.backups_mock.get.assert_called_with(self.backup.id)
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.data, data)
|
@ -1,376 +0,0 @@
|
||||
# Copyright 2015 iWeb Technologies Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
"""Volume v1 QoS action implementations"""
|
||||
|
||||
import logging
|
||||
|
||||
from osc_lib.cli import format_columns
|
||||
from osc_lib.cli import parseractions
|
||||
from osc_lib.command import command
|
||||
from osc_lib import exceptions
|
||||
from osc_lib import utils
|
||||
|
||||
from openstackclient.i18n import _
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AssociateQos(command.Command):
|
||||
_description = _("Associate a QoS specification to a volume type")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'qos_spec',
|
||||
metavar='<qos-spec>',
|
||||
help=_('QoS specification to modify (name or ID)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'volume_type',
|
||||
metavar='<volume-type>',
|
||||
help=_('Volume type to associate the QoS (name or ID)'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
qos_spec = utils.find_resource(
|
||||
volume_client.qos_specs, parsed_args.qos_spec
|
||||
)
|
||||
volume_type = utils.find_resource(
|
||||
volume_client.volume_types, parsed_args.volume_type
|
||||
)
|
||||
|
||||
volume_client.qos_specs.associate(qos_spec.id, volume_type.id)
|
||||
|
||||
|
||||
class CreateQos(command.ShowOne):
|
||||
_description = _("Create new QoS specification")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'name',
|
||||
metavar='<name>',
|
||||
help=_('New QoS specification name'),
|
||||
)
|
||||
consumer_choices = ['front-end', 'back-end', 'both']
|
||||
parser.add_argument(
|
||||
'--consumer',
|
||||
metavar='<consumer>',
|
||||
choices=consumer_choices,
|
||||
default='both',
|
||||
help=(
|
||||
_(
|
||||
'Consumer of the QoS. Valid consumers: %s '
|
||||
"(defaults to 'both')"
|
||||
)
|
||||
% utils.format_list(consumer_choices)
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--property',
|
||||
metavar='<key=value>',
|
||||
action=parseractions.KeyValueAction,
|
||||
help=_(
|
||||
'Set a QoS specification property '
|
||||
'(repeat option to set multiple properties)'
|
||||
),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
specs = {}
|
||||
specs.update({'consumer': parsed_args.consumer})
|
||||
|
||||
if parsed_args.property:
|
||||
specs.update(parsed_args.property)
|
||||
|
||||
qos_spec = volume_client.qos_specs.create(parsed_args.name, specs)
|
||||
qos_spec._info.update(
|
||||
{
|
||||
'properties': format_columns.DictColumn(
|
||||
qos_spec._info.pop('specs')
|
||||
)
|
||||
}
|
||||
)
|
||||
return zip(*sorted(qos_spec._info.items()))
|
||||
|
||||
|
||||
class DeleteQos(command.Command):
|
||||
_description = _("Delete QoS specification")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'qos_specs',
|
||||
metavar='<qos-spec>',
|
||||
nargs="+",
|
||||
help=_('QoS specification(s) to delete (name or ID)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--force',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_("Allow to delete in-use QoS specification(s)"),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
result = 0
|
||||
|
||||
for i in parsed_args.qos_specs:
|
||||
try:
|
||||
qos_spec = utils.find_resource(volume_client.qos_specs, i)
|
||||
volume_client.qos_specs.delete(qos_spec.id, parsed_args.force)
|
||||
except Exception as e:
|
||||
result += 1
|
||||
LOG.error(
|
||||
_(
|
||||
"Failed to delete QoS specification with "
|
||||
"name or ID '%(qos)s': %(e)s"
|
||||
),
|
||||
{'qos': i, 'e': e},
|
||||
)
|
||||
|
||||
if result > 0:
|
||||
total = len(parsed_args.qos_specs)
|
||||
msg = _(
|
||||
"%(result)s of %(total)s QoS specifications failed to delete."
|
||||
) % {'result': result, 'total': total}
|
||||
raise exceptions.CommandError(msg)
|
||||
|
||||
|
||||
class DisassociateQos(command.Command):
|
||||
_description = _("Disassociate a QoS specification from a volume type")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'qos_spec',
|
||||
metavar='<qos-spec>',
|
||||
help=_('QoS specification to modify (name or ID)'),
|
||||
)
|
||||
volume_type_group = parser.add_mutually_exclusive_group()
|
||||
volume_type_group.add_argument(
|
||||
'--volume-type',
|
||||
metavar='<volume-type>',
|
||||
help=_('Volume type to disassociate the QoS from (name or ID)'),
|
||||
)
|
||||
volume_type_group.add_argument(
|
||||
'--all',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Disassociate the QoS from every volume type'),
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
qos_spec = utils.find_resource(
|
||||
volume_client.qos_specs, parsed_args.qos_spec
|
||||
)
|
||||
|
||||
if parsed_args.volume_type:
|
||||
volume_type = utils.find_resource(
|
||||
volume_client.volume_types, parsed_args.volume_type
|
||||
)
|
||||
volume_client.qos_specs.disassociate(qos_spec.id, volume_type.id)
|
||||
elif parsed_args.all:
|
||||
volume_client.qos_specs.disassociate_all(qos_spec.id)
|
||||
|
||||
|
||||
class ListQos(command.Lister):
|
||||
_description = _("List QoS specifications")
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
qos_specs_list = volume_client.qos_specs.list()
|
||||
|
||||
for qos in qos_specs_list:
|
||||
try:
|
||||
qos_associations = volume_client.qos_specs.get_associations(
|
||||
qos,
|
||||
)
|
||||
if qos_associations:
|
||||
associations = [
|
||||
association.name for association in qos_associations
|
||||
]
|
||||
qos._info.update({'associations': associations})
|
||||
except Exception as ex:
|
||||
if type(ex).__name__ == 'NotFound':
|
||||
qos._info.update({'associations': None})
|
||||
else:
|
||||
raise
|
||||
|
||||
display_columns = (
|
||||
'ID',
|
||||
'Name',
|
||||
'Consumer',
|
||||
'Associations',
|
||||
'Properties',
|
||||
)
|
||||
columns = ('ID', 'Name', 'Consumer', 'Associations', 'Specs')
|
||||
return (
|
||||
display_columns,
|
||||
(
|
||||
utils.get_dict_properties(
|
||||
s._info,
|
||||
columns,
|
||||
formatters={
|
||||
'Specs': format_columns.DictColumn,
|
||||
'Associations': format_columns.ListColumn,
|
||||
},
|
||||
)
|
||||
for s in qos_specs_list
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class SetQos(command.Command):
|
||||
_description = _("Set QoS specification properties")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'qos_spec',
|
||||
metavar='<qos-spec>',
|
||||
help=_('QoS specification to modify (name or ID)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--no-property',
|
||||
dest='no_property',
|
||||
action='store_true',
|
||||
help=_(
|
||||
'Remove all properties from <qos-spec> '
|
||||
'(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 or modify for this QoS specification '
|
||||
'(repeat option to set multiple properties)'
|
||||
),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
qos_spec = utils.find_resource(
|
||||
volume_client.qos_specs, parsed_args.qos_spec
|
||||
)
|
||||
|
||||
result = 0
|
||||
if parsed_args.no_property:
|
||||
try:
|
||||
key_list = list(qos_spec._info['specs'].keys())
|
||||
volume_client.qos_specs.unset_keys(qos_spec.id, key_list)
|
||||
except Exception as e:
|
||||
LOG.error(_("Failed to clean qos properties: %s"), e)
|
||||
result += 1
|
||||
|
||||
if parsed_args.property:
|
||||
try:
|
||||
volume_client.qos_specs.set_keys(
|
||||
qos_spec.id,
|
||||
parsed_args.property,
|
||||
)
|
||||
except Exception as e:
|
||||
LOG.error(_("Failed to set qos property: %s"), e)
|
||||
result += 1
|
||||
|
||||
if result > 0:
|
||||
raise exceptions.CommandError(
|
||||
_("One or more of the set operations failed")
|
||||
)
|
||||
|
||||
|
||||
class ShowQos(command.ShowOne):
|
||||
_description = _("Display QoS specification details")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'qos_spec',
|
||||
metavar='<qos-spec>',
|
||||
help=_('QoS specification to display (name or ID)'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
qos_spec = utils.find_resource(
|
||||
volume_client.qos_specs, parsed_args.qos_spec
|
||||
)
|
||||
|
||||
qos_associations = volume_client.qos_specs.get_associations(qos_spec)
|
||||
if qos_associations:
|
||||
associations = [
|
||||
association.name for association in qos_associations
|
||||
]
|
||||
qos_spec._info.update(
|
||||
{'associations': format_columns.ListColumn(associations)}
|
||||
)
|
||||
qos_spec._info.update(
|
||||
{
|
||||
'properties': format_columns.DictColumn(
|
||||
qos_spec._info.pop('specs')
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
return zip(*sorted(qos_spec._info.items()))
|
||||
|
||||
|
||||
class UnsetQos(command.Command):
|
||||
_description = _("Unset QoS specification properties")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'qos_spec',
|
||||
metavar='<qos-spec>',
|
||||
help=_('QoS specification to modify (name or ID)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--property',
|
||||
metavar='<key>',
|
||||
action='append',
|
||||
help=_(
|
||||
'Property to remove from the QoS specification. '
|
||||
'(repeat option to unset multiple properties)'
|
||||
),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
qos_spec = utils.find_resource(
|
||||
volume_client.qos_specs, parsed_args.qos_spec
|
||||
)
|
||||
|
||||
if parsed_args.property:
|
||||
volume_client.qos_specs.unset_keys(
|
||||
qos_spec.id, parsed_args.property
|
||||
)
|
@ -1,136 +0,0 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
"""Service action implementations"""
|
||||
|
||||
from osc_lib.command import command
|
||||
from osc_lib import exceptions
|
||||
from osc_lib import utils
|
||||
|
||||
from openstackclient.i18n import _
|
||||
|
||||
|
||||
class ListService(command.Lister):
|
||||
_description = _("List service command")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
"--host",
|
||||
metavar="<host>",
|
||||
help=_("List services on specified host (name only)"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--service",
|
||||
metavar="<service>",
|
||||
help=_("List only specified service (name only)"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--long",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=_("List additional fields in output"),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
service_client = self.app.client_manager.volume
|
||||
|
||||
if parsed_args.long:
|
||||
columns = [
|
||||
"Binary",
|
||||
"Host",
|
||||
"Zone",
|
||||
"Status",
|
||||
"State",
|
||||
"Updated At",
|
||||
"Disabled Reason",
|
||||
]
|
||||
else:
|
||||
columns = [
|
||||
"Binary",
|
||||
"Host",
|
||||
"Zone",
|
||||
"Status",
|
||||
"State",
|
||||
"Updated At",
|
||||
]
|
||||
|
||||
data = service_client.services.list(
|
||||
parsed_args.host, parsed_args.service
|
||||
)
|
||||
return (
|
||||
columns,
|
||||
(
|
||||
utils.get_item_properties(
|
||||
s,
|
||||
columns,
|
||||
)
|
||||
for s in data
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class SetService(command.Command):
|
||||
_description = _("Set volume service properties")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument("host", metavar="<host>", help=_("Name of host"))
|
||||
parser.add_argument(
|
||||
"service",
|
||||
metavar="<service>",
|
||||
help=_("Name of service (Binary name)"),
|
||||
)
|
||||
enabled_group = parser.add_mutually_exclusive_group()
|
||||
enabled_group.add_argument(
|
||||
"--enable", action="store_true", help=_("Enable volume service")
|
||||
)
|
||||
enabled_group.add_argument(
|
||||
"--disable", action="store_true", help=_("Disable volume service")
|
||||
)
|
||||
parser.add_argument(
|
||||
"--disable-reason",
|
||||
metavar="<reason>",
|
||||
help=_(
|
||||
"Reason for disabling the service "
|
||||
"(should be used with --disable option)"
|
||||
),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
if parsed_args.disable_reason and not parsed_args.disable:
|
||||
msg = _(
|
||||
"Cannot specify option --disable-reason without "
|
||||
"--disable specified."
|
||||
)
|
||||
raise exceptions.CommandError(msg)
|
||||
|
||||
service_client = self.app.client_manager.volume
|
||||
if parsed_args.enable:
|
||||
service_client.services.enable(
|
||||
parsed_args.host, parsed_args.service
|
||||
)
|
||||
if parsed_args.disable:
|
||||
if parsed_args.disable_reason:
|
||||
service_client.services.disable_log_reason(
|
||||
parsed_args.host,
|
||||
parsed_args.service,
|
||||
parsed_args.disable_reason,
|
||||
)
|
||||
else:
|
||||
service_client.services.disable(
|
||||
parsed_args.host, parsed_args.service
|
||||
)
|
@ -1,727 +0,0 @@
|
||||
# Copyright 2012-2013 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
"""Volume v1 Volume action implementations"""
|
||||
|
||||
import argparse
|
||||
import functools
|
||||
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 import exceptions
|
||||
from osc_lib import utils
|
||||
|
||||
from openstackclient.common import pagination
|
||||
from openstackclient.i18n import _
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AttachmentsColumn(cliff_columns.FormattableColumn):
|
||||
"""Formattable column for attachments column.
|
||||
|
||||
Unlike the parent FormattableColumn class, the initializer of the
|
||||
class takes server_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(AttachmentsColumn, server_cache)``.
|
||||
"""
|
||||
|
||||
def __init__(self, value, server_cache=None):
|
||||
super().__init__(value)
|
||||
self._server_cache = server_cache or {}
|
||||
|
||||
def human_readable(self):
|
||||
"""Return a formatted string of a volume's attached instances
|
||||
|
||||
:rtype: a string of formatted instances
|
||||
"""
|
||||
|
||||
msg = ''
|
||||
for attachment in self._value:
|
||||
server = attachment['server_id']
|
||||
if server in self._server_cache.keys():
|
||||
server = self._server_cache[server].name
|
||||
device = attachment['device']
|
||||
msg += f'Attached to {server} on {device} '
|
||||
return msg
|
||||
|
||||
|
||||
def _check_size_arg(args):
|
||||
"""Check whether --size option is required or not.
|
||||
|
||||
Require size parameter only in case when snapshot or source
|
||||
volume is not specified.
|
||||
"""
|
||||
|
||||
if (args.snapshot or args.source) is None and args.size is None:
|
||||
msg = _(
|
||||
"--size is a required option if snapshot "
|
||||
"or source volume is not specified."
|
||||
)
|
||||
raise exceptions.CommandError(msg)
|
||||
|
||||
|
||||
class CreateVolume(command.ShowOne):
|
||||
_description = _("Create new volume")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'name',
|
||||
metavar='<name>',
|
||||
help=_('Volume name'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--size',
|
||||
metavar='<size>',
|
||||
type=int,
|
||||
help=_(
|
||||
"Volume size in GB (Required unless --snapshot or "
|
||||
"--source is specified)"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--type',
|
||||
metavar='<volume-type>',
|
||||
help=_("Set the type of volume"),
|
||||
)
|
||||
source_group = parser.add_mutually_exclusive_group()
|
||||
source_group.add_argument(
|
||||
'--image',
|
||||
metavar='<image>',
|
||||
help=_('Use <image> as source of volume (name or ID)'),
|
||||
)
|
||||
source_group.add_argument(
|
||||
'--snapshot',
|
||||
metavar='<snapshot>',
|
||||
help=_('Use <snapshot> as source of volume (name or ID)'),
|
||||
)
|
||||
source_group.add_argument(
|
||||
'--snapshot-id',
|
||||
metavar='<snapshot-id>',
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
source_group.add_argument(
|
||||
'--source',
|
||||
metavar='<volume>',
|
||||
help=_('Volume to clone (name or ID)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--description',
|
||||
metavar='<description>',
|
||||
help=_('Volume description'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--user',
|
||||
metavar='<user>',
|
||||
help=_('Specify an alternate user (name or ID)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--project',
|
||||
metavar='<project>',
|
||||
help=_('Specify an alternate project (name or ID)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--availability-zone',
|
||||
metavar='<availability-zone>',
|
||||
help=_('Create volume in <availability-zone>'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--property',
|
||||
metavar='<key=value>',
|
||||
action=parseractions.KeyValueAction,
|
||||
help=_(
|
||||
'Set a property on this volume '
|
||||
'(repeat option to set multiple properties)'
|
||||
),
|
||||
)
|
||||
bootable_group = parser.add_mutually_exclusive_group()
|
||||
bootable_group.add_argument(
|
||||
"--bootable",
|
||||
action="store_true",
|
||||
help=_("Mark volume as bootable"),
|
||||
)
|
||||
bootable_group.add_argument(
|
||||
"--non-bootable",
|
||||
action="store_true",
|
||||
help=_("Mark volume as non-bootable (default)"),
|
||||
)
|
||||
readonly_group = parser.add_mutually_exclusive_group()
|
||||
readonly_group.add_argument(
|
||||
"--read-only",
|
||||
action="store_true",
|
||||
help=_("Set volume to read-only access mode"),
|
||||
)
|
||||
readonly_group.add_argument(
|
||||
"--read-write",
|
||||
action="store_true",
|
||||
help=_("Set volume to read-write access mode (default)"),
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
_check_size_arg(parsed_args)
|
||||
identity_client = self.app.client_manager.identity
|
||||
image_client = self.app.client_manager.image
|
||||
volume_client = self.app.client_manager.volume
|
||||
|
||||
source_volume = None
|
||||
if parsed_args.source:
|
||||
source_volume = utils.find_resource(
|
||||
volume_client.volumes,
|
||||
parsed_args.source,
|
||||
).id
|
||||
|
||||
project = None
|
||||
if parsed_args.project:
|
||||
project = utils.find_resource(
|
||||
identity_client.tenants,
|
||||
parsed_args.project,
|
||||
).id
|
||||
|
||||
user = None
|
||||
if parsed_args.user:
|
||||
user = utils.find_resource(
|
||||
identity_client.users,
|
||||
parsed_args.user,
|
||||
).id
|
||||
|
||||
image = None
|
||||
if parsed_args.image:
|
||||
image = image_client.find_image(
|
||||
parsed_args.image,
|
||||
ignore_missing=False,
|
||||
).id
|
||||
|
||||
snapshot = parsed_args.snapshot or parsed_args.snapshot_id
|
||||
|
||||
volume = volume_client.volumes.create(
|
||||
parsed_args.size,
|
||||
snapshot,
|
||||
source_volume,
|
||||
parsed_args.name,
|
||||
parsed_args.description,
|
||||
parsed_args.type,
|
||||
user,
|
||||
project,
|
||||
parsed_args.availability_zone,
|
||||
parsed_args.property,
|
||||
image,
|
||||
)
|
||||
|
||||
if parsed_args.bootable or parsed_args.non_bootable:
|
||||
try:
|
||||
if utils.wait_for_status(
|
||||
volume_client.volumes.get,
|
||||
volume.id,
|
||||
success_status=['available'],
|
||||
error_status=['error'],
|
||||
sleep_time=1,
|
||||
):
|
||||
volume_client.volumes.set_bootable(
|
||||
volume.id, parsed_args.bootable
|
||||
)
|
||||
else:
|
||||
msg = _(
|
||||
"Volume status is not available for setting boot state"
|
||||
)
|
||||
raise exceptions.CommandError(msg)
|
||||
except Exception as e:
|
||||
LOG.error(_("Failed to set volume bootable property: %s"), e)
|
||||
if parsed_args.read_only or parsed_args.read_write:
|
||||
try:
|
||||
if utils.wait_for_status(
|
||||
volume_client.volumes.get,
|
||||
volume.id,
|
||||
success_status=['available'],
|
||||
error_status=['error'],
|
||||
sleep_time=1,
|
||||
):
|
||||
volume_client.volumes.update_readonly_flag(
|
||||
volume.id, parsed_args.read_only
|
||||
)
|
||||
else:
|
||||
msg = _(
|
||||
"Volume status is not available for setting it"
|
||||
"read only."
|
||||
)
|
||||
raise exceptions.CommandError(msg)
|
||||
except Exception as e:
|
||||
LOG.error(
|
||||
_("Failed to set volume read-only access mode flag: %s"),
|
||||
e,
|
||||
)
|
||||
|
||||
# Map 'metadata' column to 'properties'
|
||||
volume._info.update(
|
||||
{
|
||||
'properties': format_columns.DictColumn(
|
||||
volume._info.pop('metadata')
|
||||
),
|
||||
'type': volume._info.pop('volume_type'),
|
||||
},
|
||||
)
|
||||
# Replace "display_name" by "name", keep consistent in v1 and v2
|
||||
if 'display_name' in volume._info:
|
||||
volume._info.update({'name': volume._info.pop('display_name')})
|
||||
volume_info = utils.backward_compat_col_showone(
|
||||
volume._info, parsed_args.columns, {'display_name': 'name'}
|
||||
)
|
||||
|
||||
return zip(*sorted(volume_info.items()))
|
||||
|
||||
|
||||
class DeleteVolume(command.Command):
|
||||
_description = _("Delete volume(s)")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'volumes',
|
||||
metavar='<volume>',
|
||||
nargs="+",
|
||||
help=_('Volume(s) to delete (name or ID)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--force',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_(
|
||||
'Attempt forced removal of volume(s), regardless of state '
|
||||
'(defaults to False)'
|
||||
),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
result = 0
|
||||
|
||||
for i in parsed_args.volumes:
|
||||
try:
|
||||
volume_obj = utils.find_resource(volume_client.volumes, i)
|
||||
if parsed_args.force:
|
||||
volume_client.volumes.force_delete(volume_obj.id)
|
||||
else:
|
||||
volume_client.volumes.delete(volume_obj.id)
|
||||
except Exception as e:
|
||||
result += 1
|
||||
LOG.error(
|
||||
_(
|
||||
"Failed to delete volume with "
|
||||
"name or ID '%(volume)s': %(e)s"
|
||||
),
|
||||
{'volume': i, 'e': e},
|
||||
)
|
||||
|
||||
if result > 0:
|
||||
total = len(parsed_args.volumes)
|
||||
msg = _("%(result)s of %(total)s volumes failed to delete.") % {
|
||||
'result': result,
|
||||
'total': total,
|
||||
}
|
||||
raise exceptions.CommandError(msg)
|
||||
|
||||
|
||||
class ListVolume(command.Lister):
|
||||
_description = _("List volumes")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--name',
|
||||
metavar='<name>',
|
||||
help=_('Filter results by volume name'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--status',
|
||||
metavar='<status>',
|
||||
help=_('Filter results by status'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--all-projects',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Include all projects (admin only)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--long',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('List additional fields in output'),
|
||||
)
|
||||
pagination.add_offset_pagination_option_to_parser(parser)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
|
||||
if parsed_args.long:
|
||||
columns: tuple[str, ...] = (
|
||||
'ID',
|
||||
'Display Name',
|
||||
'Status',
|
||||
'Size',
|
||||
'Volume Type',
|
||||
'Bootable',
|
||||
'Attachments',
|
||||
'Metadata',
|
||||
)
|
||||
column_headers: tuple[str, ...] = (
|
||||
'ID',
|
||||
'Name',
|
||||
'Status',
|
||||
'Size',
|
||||
'Type',
|
||||
'Bootable',
|
||||
'Attached to',
|
||||
'Properties',
|
||||
)
|
||||
else:
|
||||
columns = (
|
||||
'ID',
|
||||
'Display Name',
|
||||
'Status',
|
||||
'Size',
|
||||
'Attachments',
|
||||
)
|
||||
column_headers = (
|
||||
'ID',
|
||||
'Name',
|
||||
'Status',
|
||||
'Size',
|
||||
'Attached to',
|
||||
)
|
||||
|
||||
# Cache the server list
|
||||
server_cache = {}
|
||||
try:
|
||||
compute_client = self.app.client_manager.sdk_connection.compute
|
||||
for s in compute_client.servers():
|
||||
server_cache[s.id] = s
|
||||
except Exception: # noqa: S110
|
||||
# Just forget it if there's any trouble
|
||||
pass
|
||||
AttachmentsColumnWithCache = functools.partial(
|
||||
AttachmentsColumn, server_cache=server_cache
|
||||
)
|
||||
|
||||
search_opts = {
|
||||
'all_tenants': parsed_args.all_projects,
|
||||
'display_name': parsed_args.name,
|
||||
'status': parsed_args.status,
|
||||
}
|
||||
|
||||
if parsed_args.offset:
|
||||
search_opts['offset'] = parsed_args.offset
|
||||
|
||||
data = volume_client.volumes.list(
|
||||
search_opts=search_opts,
|
||||
limit=parsed_args.limit,
|
||||
)
|
||||
column_headers = utils.backward_compat_col_lister(
|
||||
column_headers, parsed_args.columns, {'Display Name': 'Name'}
|
||||
)
|
||||
|
||||
return (
|
||||
column_headers,
|
||||
(
|
||||
utils.get_item_properties(
|
||||
s,
|
||||
columns,
|
||||
formatters={
|
||||
'Metadata': format_columns.DictColumn,
|
||||
'Attachments': AttachmentsColumnWithCache,
|
||||
},
|
||||
)
|
||||
for s in data
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class MigrateVolume(command.Command):
|
||||
_description = _("Migrate volume to a new host")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'volume',
|
||||
metavar="<volume>",
|
||||
help=_("Volume to migrate (name or ID)"),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--host',
|
||||
metavar="<host>",
|
||||
required=True,
|
||||
help=_(
|
||||
"Destination host (takes the form: host@backend-name#pool)"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--force-host-copy',
|
||||
action="store_true",
|
||||
help=_(
|
||||
"Enable generic host-based force-migration, "
|
||||
"which bypasses driver optimizations"
|
||||
),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
volume = utils.find_resource(volume_client.volumes, parsed_args.volume)
|
||||
volume_client.volumes.migrate_volume(
|
||||
volume.id,
|
||||
parsed_args.host,
|
||||
parsed_args.force_host_copy,
|
||||
)
|
||||
|
||||
|
||||
class SetVolume(command.Command):
|
||||
_description = _("Set volume properties")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'volume',
|
||||
metavar='<volume>',
|
||||
help=_('Volume to modify (name or ID)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--name',
|
||||
metavar='<name>',
|
||||
help=_('New volume name'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--description',
|
||||
metavar='<description>',
|
||||
help=_('New volume description'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--size',
|
||||
metavar='<size>',
|
||||
type=int,
|
||||
help=_('Extend volume size in GB'),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-property",
|
||||
dest="no_property",
|
||||
action="store_true",
|
||||
help=_(
|
||||
"Remove all properties from <volume> "
|
||||
"(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=_(
|
||||
'Set a property on this volume '
|
||||
'(repeat option to set multiple properties)'
|
||||
),
|
||||
)
|
||||
bootable_group = parser.add_mutually_exclusive_group()
|
||||
bootable_group.add_argument(
|
||||
"--bootable",
|
||||
action="store_true",
|
||||
help=_("Mark volume as bootable"),
|
||||
)
|
||||
bootable_group.add_argument(
|
||||
"--non-bootable",
|
||||
action="store_true",
|
||||
help=_("Mark volume as non-bootable"),
|
||||
)
|
||||
readonly_group = parser.add_mutually_exclusive_group()
|
||||
readonly_group.add_argument(
|
||||
"--read-only",
|
||||
action="store_true",
|
||||
help=_("Set volume to read-only access mode"),
|
||||
)
|
||||
readonly_group.add_argument(
|
||||
"--read-write",
|
||||
action="store_true",
|
||||
help=_("Set volume to read-write access mode"),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
volume = utils.find_resource(volume_client.volumes, parsed_args.volume)
|
||||
|
||||
result = 0
|
||||
if parsed_args.size:
|
||||
try:
|
||||
if volume.status != 'available':
|
||||
msg = (
|
||||
_(
|
||||
"Volume is in %s state, it must be available "
|
||||
"before size can be extended"
|
||||
)
|
||||
% volume.status
|
||||
)
|
||||
raise exceptions.CommandError(msg)
|
||||
if parsed_args.size <= volume.size:
|
||||
msg = (
|
||||
_("New size must be greater than %s GB") % volume.size
|
||||
)
|
||||
raise exceptions.CommandError(msg)
|
||||
volume_client.volumes.extend(volume.id, parsed_args.size)
|
||||
except Exception as e:
|
||||
LOG.error(_("Failed to set volume size: %s"), e)
|
||||
result += 1
|
||||
|
||||
if parsed_args.no_property:
|
||||
try:
|
||||
volume_client.volumes.delete_metadata(
|
||||
volume.id, volume.metadata.keys()
|
||||
)
|
||||
except Exception as e:
|
||||
LOG.error(_("Failed to clean volume properties: %s"), e)
|
||||
result += 1
|
||||
|
||||
if parsed_args.property:
|
||||
try:
|
||||
volume_client.volumes.set_metadata(
|
||||
volume.id, parsed_args.property
|
||||
)
|
||||
except Exception as e:
|
||||
LOG.error(_("Failed to set volume property: %s"), e)
|
||||
result += 1
|
||||
if parsed_args.bootable or parsed_args.non_bootable:
|
||||
try:
|
||||
volume_client.volumes.set_bootable(
|
||||
volume.id, parsed_args.bootable
|
||||
)
|
||||
except Exception as e:
|
||||
LOG.error(_("Failed to set volume bootable property: %s"), e)
|
||||
result += 1
|
||||
if parsed_args.read_only or parsed_args.read_write:
|
||||
try:
|
||||
volume_client.volumes.update_readonly_flag(
|
||||
volume.id, parsed_args.read_only
|
||||
)
|
||||
except Exception as e:
|
||||
LOG.error(
|
||||
_("Failed to set volume read-only access mode flag: %s"),
|
||||
e,
|
||||
)
|
||||
result += 1
|
||||
kwargs = {}
|
||||
if parsed_args.name:
|
||||
kwargs['display_name'] = parsed_args.name
|
||||
if parsed_args.description:
|
||||
kwargs['display_description'] = parsed_args.description
|
||||
if kwargs:
|
||||
try:
|
||||
volume_client.volumes.update(volume.id, **kwargs)
|
||||
except Exception as e:
|
||||
LOG.error(
|
||||
_(
|
||||
"Failed to update volume display name "
|
||||
"or display description: %s"
|
||||
),
|
||||
e,
|
||||
)
|
||||
result += 1
|
||||
|
||||
if result > 0:
|
||||
raise exceptions.CommandError(
|
||||
_("One or more of the set operations failed")
|
||||
)
|
||||
|
||||
|
||||
class ShowVolume(command.ShowOne):
|
||||
_description = _("Show volume details")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'volume',
|
||||
metavar='<volume>',
|
||||
help=_('Volume to display (name or ID)'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
volume = utils.find_resource(volume_client.volumes, parsed_args.volume)
|
||||
# Map 'metadata' column to 'properties'
|
||||
volume._info.update(
|
||||
{
|
||||
'properties': format_columns.DictColumn(
|
||||
volume._info.pop('metadata')
|
||||
),
|
||||
'type': volume._info.pop('volume_type'),
|
||||
},
|
||||
)
|
||||
if 'os-vol-tenant-attr:tenant_id' in volume._info:
|
||||
volume._info.update(
|
||||
{
|
||||
'project_id': volume._info.pop(
|
||||
'os-vol-tenant-attr:tenant_id'
|
||||
)
|
||||
}
|
||||
)
|
||||
# Replace "display_name" by "name", keep consistent in v1 and v2
|
||||
if 'display_name' in volume._info:
|
||||
volume._info.update({'name': volume._info.pop('display_name')})
|
||||
|
||||
volume_info = utils.backward_compat_col_showone(
|
||||
volume._info, parsed_args.columns, {'display_name': 'name'}
|
||||
)
|
||||
|
||||
return zip(*sorted(volume_info.items()))
|
||||
|
||||
|
||||
class UnsetVolume(command.Command):
|
||||
_description = _("Unset volume properties")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'volume',
|
||||
metavar='<volume>',
|
||||
help=_('Volume to modify (name or ID)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--property',
|
||||
metavar='<key>',
|
||||
action='append',
|
||||
help=_(
|
||||
'Remove a property from volume '
|
||||
'(repeat option to remove multiple properties)'
|
||||
),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
volume = utils.find_resource(volume_client.volumes, parsed_args.volume)
|
||||
|
||||
if parsed_args.property:
|
||||
volume_client.volumes.delete_metadata(
|
||||
volume.id,
|
||||
parsed_args.property,
|
||||
)
|
@ -1,301 +0,0 @@
|
||||
# Copyright 2012-2013 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
"""Volume v1 Backup action implementations"""
|
||||
|
||||
import copy
|
||||
import functools
|
||||
import logging
|
||||
|
||||
from cliff import columns as cliff_columns
|
||||
from osc_lib.command import command
|
||||
from osc_lib import exceptions
|
||||
from osc_lib import utils
|
||||
|
||||
from openstackclient.i18n import _
|
||||
|
||||
|
||||
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].display_name
|
||||
return volume
|
||||
|
||||
|
||||
class CreateVolumeBackup(command.ShowOne):
|
||||
_description = _("Create new volume backup")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'volume',
|
||||
metavar='<volume>',
|
||||
help=_('Volume to backup (name or ID)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--container',
|
||||
metavar='<container>',
|
||||
required=False,
|
||||
help=_('Optional backup container name'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--name',
|
||||
metavar='<name>',
|
||||
help=_('Name of the backup'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--description',
|
||||
metavar='<description>',
|
||||
help=_('Description of the backup'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
volume_id = utils.find_resource(
|
||||
volume_client.volumes, parsed_args.volume
|
||||
).id
|
||||
backup = volume_client.backups.create(
|
||||
volume_id,
|
||||
parsed_args.container,
|
||||
parsed_args.name,
|
||||
parsed_args.description,
|
||||
)
|
||||
|
||||
backup._info.pop('links')
|
||||
return zip(*sorted(backup._info.items()))
|
||||
|
||||
|
||||
class DeleteVolumeBackup(command.Command):
|
||||
_description = _("Delete volume backup(s)")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'backups',
|
||||
metavar='<backup>',
|
||||
nargs="+",
|
||||
help=_('Backup(s) to delete (name or ID)'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
result = 0
|
||||
|
||||
for i in parsed_args.backups:
|
||||
try:
|
||||
backup_id = utils.find_resource(volume_client.backups, i).id
|
||||
volume_client.backups.delete(backup_id)
|
||||
except Exception as e:
|
||||
result += 1
|
||||
LOG.error(
|
||||
_(
|
||||
"Failed to delete backup with "
|
||||
"name or ID '%(backup)s': %(e)s"
|
||||
),
|
||||
{'backup': i, 'e': e},
|
||||
)
|
||||
|
||||
if result > 0:
|
||||
total = len(parsed_args.backups)
|
||||
msg = _("%(result)s of %(total)s backups failed to delete.") % {
|
||||
'result': result,
|
||||
'total': total,
|
||||
}
|
||||
raise exceptions.CommandError(msg)
|
||||
|
||||
|
||||
class ListVolumeBackup(command.Lister):
|
||||
_description = _("List volume backups")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--long',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('List additional fields in output'),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--name",
|
||||
metavar="<name>",
|
||||
help=_("Filters results by the backup name"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--status",
|
||||
metavar="<status>",
|
||||
choices=[
|
||||
'creating',
|
||||
'available',
|
||||
'deleting',
|
||||
'error',
|
||||
'restoring',
|
||||
'error_restoring',
|
||||
],
|
||||
help=_(
|
||||
"Filters results by the backup status "
|
||||
"('creating', 'available', 'deleting', "
|
||||
"'error', 'restoring' or 'error_restoring')"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--volume",
|
||||
metavar="<volume>",
|
||||
help=_(
|
||||
"Filters results by the volume which they backup (name or ID)"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--all-projects',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Include all projects (admin only)'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
|
||||
if parsed_args.long:
|
||||
columns = [
|
||||
'ID',
|
||||
'Name',
|
||||
'Description',
|
||||
'Status',
|
||||
'Size',
|
||||
'Availability Zone',
|
||||
'Volume ID',
|
||||
'Container',
|
||||
]
|
||||
column_headers = copy.deepcopy(columns)
|
||||
column_headers[6] = 'Volume'
|
||||
else:
|
||||
columns = ['ID', 'Name', 'Description', 'Status', 'Size']
|
||||
column_headers = 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
|
||||
VolumeIdColumnWithCache = functools.partial(
|
||||
VolumeIdColumn, volume_cache=volume_cache
|
||||
)
|
||||
|
||||
filter_volume_id = None
|
||||
if parsed_args.volume:
|
||||
filter_volume_id = utils.find_resource(
|
||||
volume_client.volumes, parsed_args.volume
|
||||
).id
|
||||
search_opts = {
|
||||
'name': parsed_args.name,
|
||||
'status': parsed_args.status,
|
||||
'volume_id': filter_volume_id,
|
||||
'all_tenants': parsed_args.all_projects,
|
||||
}
|
||||
data = volume_client.backups.list(
|
||||
search_opts=search_opts,
|
||||
)
|
||||
|
||||
return (
|
||||
column_headers,
|
||||
(
|
||||
utils.get_item_properties(
|
||||
s,
|
||||
columns,
|
||||
formatters={'Volume ID': VolumeIdColumnWithCache},
|
||||
)
|
||||
for s in data
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class RestoreVolumeBackup(command.Command):
|
||||
_description = _("Restore volume backup")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'backup',
|
||||
metavar='<backup>',
|
||||
help=_('Backup to restore (name or ID)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'volume',
|
||||
metavar='<volume>',
|
||||
nargs='?',
|
||||
help=_('Volume to restore to (name or ID) (default to None)'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
backup = utils.find_resource(
|
||||
volume_client.backups,
|
||||
parsed_args.backup,
|
||||
)
|
||||
volume_id = None
|
||||
if parsed_args.volume is not None:
|
||||
volume_id = utils.find_resource(
|
||||
volume_client.volumes,
|
||||
parsed_args.volume,
|
||||
).id
|
||||
return volume_client.restores.restore(backup.id, volume_id)
|
||||
|
||||
|
||||
class ShowVolumeBackup(command.ShowOne):
|
||||
_description = _("Display volume backup details")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'backup',
|
||||
metavar='<backup>',
|
||||
help=_('Backup to display (name or ID)'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
backup = utils.find_resource(volume_client.backups, parsed_args.backup)
|
||||
backup._info.pop('links')
|
||||
return zip(*sorted(backup._info.items()))
|
@ -1,432 +0,0 @@
|
||||
# Copyright 2012-2013 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
"""Volume v1 Snapshot action implementations"""
|
||||
|
||||
import copy
|
||||
import functools
|
||||
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 import exceptions
|
||||
from osc_lib import utils
|
||||
|
||||
from openstackclient.i18n import _
|
||||
|
||||
|
||||
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].display_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',
|
||||
dest='force',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_(
|
||||
'Create a snapshot attached to an instance. Default is False'
|
||||
),
|
||||
)
|
||||
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
|
||||
snapshot = volume_client.volume_snapshots.create(
|
||||
volume_id,
|
||||
parsed_args.force,
|
||||
parsed_args.snapshot_name,
|
||||
parsed_args.description,
|
||||
)
|
||||
|
||||
snapshot._info.update(
|
||||
{
|
||||
'properties': format_columns.DictColumn(
|
||||
snapshot._info.pop('metadata')
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
return zip(*sorted(snapshot._info.items()))
|
||||
|
||||
|
||||
class DeleteVolumeSnapshot(command.Command):
|
||||
_description = _("Delete volume snapshot(s)")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'snapshots',
|
||||
metavar='<snapshot>',
|
||||
nargs="+",
|
||||
help=_('Snapshot(s) to delete (name or ID)'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
result = 0
|
||||
|
||||
for i in parsed_args.snapshots:
|
||||
try:
|
||||
snapshot_id = utils.find_resource(
|
||||
volume_client.volume_snapshots, i
|
||||
).id
|
||||
volume_client.volume_snapshots.delete(snapshot_id)
|
||||
except Exception as e:
|
||||
result += 1
|
||||
LOG.error(
|
||||
_(
|
||||
"Failed to delete snapshot with "
|
||||
"name or ID '%(snapshot)s': %(e)s"
|
||||
),
|
||||
{'snapshot': i, 'e': e},
|
||||
)
|
||||
|
||||
if result > 0:
|
||||
total = len(parsed_args.snapshots)
|
||||
msg = _("%(result)s of %(total)s snapshots failed to delete.") % {
|
||||
'result': result,
|
||||
'total': total,
|
||||
}
|
||||
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(
|
||||
'--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).'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
|
||||
if parsed_args.long:
|
||||
columns = [
|
||||
'ID',
|
||||
'Display Name',
|
||||
'Display Description',
|
||||
'Status',
|
||||
'Size',
|
||||
'Created At',
|
||||
'Volume ID',
|
||||
'Metadata',
|
||||
]
|
||||
column_headers = copy.deepcopy(columns)
|
||||
column_headers[6] = 'Volume'
|
||||
column_headers[7] = 'Properties'
|
||||
else:
|
||||
columns = [
|
||||
'ID',
|
||||
'Display Name',
|
||||
'Display Description',
|
||||
'Status',
|
||||
'Size',
|
||||
]
|
||||
column_headers = copy.deepcopy(columns)
|
||||
|
||||
# Always update Name and Description
|
||||
column_headers[1] = 'Name'
|
||||
column_headers[2] = 'Description'
|
||||
|
||||
# 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
|
||||
VolumeIdColumnWithCache = 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
|
||||
|
||||
search_opts = {
|
||||
'all_tenants': parsed_args.all_projects,
|
||||
'display_name': parsed_args.name,
|
||||
'status': parsed_args.status,
|
||||
'volume_id': volume_id,
|
||||
}
|
||||
|
||||
data = volume_client.volume_snapshots.list(search_opts=search_opts)
|
||||
return (
|
||||
column_headers,
|
||||
(
|
||||
utils.get_item_properties(
|
||||
s,
|
||||
columns,
|
||||
formatters={
|
||||
'Metadata': format_columns.DictColumn,
|
||||
'Volume ID': VolumeIdColumnWithCache,
|
||||
},
|
||||
)
|
||||
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)'
|
||||
),
|
||||
)
|
||||
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
|
||||
|
||||
kwargs = {}
|
||||
if parsed_args.name:
|
||||
kwargs['display_name'] = parsed_args.name
|
||||
if parsed_args.description:
|
||||
kwargs['display_description'] = parsed_args.description
|
||||
if kwargs:
|
||||
try:
|
||||
snapshot.update(**kwargs)
|
||||
except Exception as e:
|
||||
LOG.error(
|
||||
_(
|
||||
"Failed to update snapshot display name "
|
||||
"or display 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',
|
||||
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,
|
||||
)
|
@ -1,200 +0,0 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
"""Volume v1 transfer action implementations"""
|
||||
|
||||
import logging
|
||||
|
||||
from osc_lib.command import command
|
||||
from osc_lib import exceptions
|
||||
from osc_lib import utils
|
||||
|
||||
from openstackclient.i18n import _
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AcceptTransferRequest(command.ShowOne):
|
||||
_description = _("Accept volume transfer request.")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'transfer_request',
|
||||
metavar="<transfer-request-id>",
|
||||
help=_('Volume transfer request to accept (ID only)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--auth-key',
|
||||
metavar="<key>",
|
||||
help=_('Volume transfer request authentication key'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
|
||||
try:
|
||||
transfer_request_id = utils.find_resource(
|
||||
volume_client.transfers, parsed_args.transfer_request
|
||||
).id
|
||||
except exceptions.CommandError:
|
||||
# Non-admin users will fail to lookup name -> ID so we just
|
||||
# move on and attempt with the user-supplied information
|
||||
transfer_request_id = parsed_args.transfer_request
|
||||
|
||||
if not parsed_args.auth_key:
|
||||
msg = _("argument --auth-key is required")
|
||||
raise exceptions.CommandError(msg)
|
||||
|
||||
transfer_accept = volume_client.transfers.accept(
|
||||
transfer_request_id,
|
||||
parsed_args.auth_key,
|
||||
)
|
||||
transfer_accept._info.pop("links", None)
|
||||
|
||||
return zip(*sorted(transfer_accept._info.items()))
|
||||
|
||||
|
||||
class CreateTransferRequest(command.ShowOne):
|
||||
_description = _("Create volume transfer request.")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--name',
|
||||
metavar="<name>",
|
||||
help=_('New transfer request name (default to None)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'volume',
|
||||
metavar="<volume>",
|
||||
help=_('Volume to transfer (name or ID)'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
volume_id = utils.find_resource(
|
||||
volume_client.volumes,
|
||||
parsed_args.volume,
|
||||
).id
|
||||
volume_transfer_request = volume_client.transfers.create(
|
||||
volume_id,
|
||||
parsed_args.name,
|
||||
)
|
||||
volume_transfer_request._info.pop("links", None)
|
||||
|
||||
return zip(*sorted(volume_transfer_request._info.items()))
|
||||
|
||||
|
||||
class DeleteTransferRequest(command.Command):
|
||||
_description = _("Delete volume transfer request(s).")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'transfer_request',
|
||||
metavar="<transfer-request>",
|
||||
nargs="+",
|
||||
help=_('Volume transfer request(s) to delete (name or ID)'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
result = 0
|
||||
|
||||
for t in parsed_args.transfer_request:
|
||||
try:
|
||||
transfer_request_id = utils.find_resource(
|
||||
volume_client.transfers,
|
||||
t,
|
||||
).id
|
||||
volume_client.transfers.delete(transfer_request_id)
|
||||
except Exception as e:
|
||||
result += 1
|
||||
LOG.error(
|
||||
_(
|
||||
"Failed to delete volume transfer request "
|
||||
"with name or ID '%(transfer)s': %(e)s"
|
||||
)
|
||||
% {'transfer': t, 'e': e}
|
||||
)
|
||||
|
||||
if result > 0:
|
||||
total = len(parsed_args.transfer_request)
|
||||
msg = _(
|
||||
"%(result)s of %(total)s volume transfer requests failed"
|
||||
" to delete"
|
||||
) % {'result': result, 'total': total}
|
||||
raise exceptions.CommandError(msg)
|
||||
|
||||
|
||||
class ListTransferRequest(command.Lister):
|
||||
_description = _("Lists all volume transfer requests.")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--all-projects',
|
||||
dest='all_projects',
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=_('Include all projects (admin only)'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
columns = ['ID', 'Name', 'Volume ID']
|
||||
column_headers = ['ID', 'Name', 'Volume']
|
||||
|
||||
volume_client = self.app.client_manager.volume
|
||||
|
||||
volume_transfer_result = volume_client.transfers.list(
|
||||
detailed=True,
|
||||
search_opts={'all_tenants': parsed_args.all_projects},
|
||||
)
|
||||
|
||||
return (
|
||||
column_headers,
|
||||
(
|
||||
utils.get_item_properties(s, columns)
|
||||
for s in volume_transfer_result
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class ShowTransferRequest(command.ShowOne):
|
||||
_description = _("Show volume transfer request details.")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'transfer_request',
|
||||
metavar="<transfer-request>",
|
||||
help=_('Volume transfer request to display (name or ID)'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
volume_transfer_request = utils.find_resource(
|
||||
volume_client.transfers,
|
||||
parsed_args.transfer_request,
|
||||
)
|
||||
volume_transfer_request._info.pop("links", None)
|
||||
|
||||
return zip(*sorted(volume_transfer_request._info.items()))
|
@ -1,519 +0,0 @@
|
||||
# Copyright 2012-2013 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
"""Volume v1 Type action implementations"""
|
||||
|
||||
import functools
|
||||
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 import exceptions
|
||||
from osc_lib import utils
|
||||
|
||||
from openstackclient.i18n import _
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EncryptionInfoColumn(cliff_columns.FormattableColumn):
|
||||
"""Formattable column for encryption info column.
|
||||
|
||||
Unlike the parent FormattableColumn class, the initializer of the
|
||||
class takes encryption_data 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(EncryptionInfoColumn encryption_data)``.
|
||||
"""
|
||||
|
||||
def __init__(self, value, encryption_data=None):
|
||||
super().__init__(value)
|
||||
self._encryption_data = encryption_data or {}
|
||||
|
||||
def _get_encryption_info(self):
|
||||
type_id = self._value
|
||||
return self._encryption_data.get(type_id)
|
||||
|
||||
def human_readable(self):
|
||||
encryption_info = self._get_encryption_info()
|
||||
if encryption_info:
|
||||
return utils.format_dict(encryption_info)
|
||||
else:
|
||||
return '-'
|
||||
|
||||
def machine_readable(self):
|
||||
return self._get_encryption_info()
|
||||
|
||||
|
||||
def _create_encryption_type(volume_client, volume_type, parsed_args):
|
||||
if not parsed_args.encryption_provider:
|
||||
msg = _(
|
||||
"'--encryption-provider' should be specified while "
|
||||
"creating a new encryption type"
|
||||
)
|
||||
raise exceptions.CommandError(msg)
|
||||
# set the default of control location while creating
|
||||
control_location = 'front-end'
|
||||
if parsed_args.encryption_control_location:
|
||||
control_location = parsed_args.encryption_control_location
|
||||
body = {
|
||||
'provider': parsed_args.encryption_provider,
|
||||
'cipher': parsed_args.encryption_cipher,
|
||||
'key_size': parsed_args.encryption_key_size,
|
||||
'control_location': control_location,
|
||||
}
|
||||
encryption = volume_client.volume_encryption_types.create(
|
||||
volume_type, body
|
||||
)
|
||||
return encryption
|
||||
|
||||
|
||||
class CreateVolumeType(command.ShowOne):
|
||||
_description = _("Create new volume type")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'name',
|
||||
metavar='<name>',
|
||||
help=_('Volume type name'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--property',
|
||||
metavar='<key=value>',
|
||||
action=parseractions.KeyValueAction,
|
||||
help=_(
|
||||
'Set a property on this volume type '
|
||||
'(repeat option to set multiple properties)'
|
||||
),
|
||||
)
|
||||
# TODO(Huanxuan Ao): Add choices for each "--encryption-*" option.
|
||||
parser.add_argument(
|
||||
'--encryption-provider',
|
||||
metavar='<provider>',
|
||||
help=_(
|
||||
'Set the encryption provider format for '
|
||||
'this volume type (e.g "luks" or "plain") (admin only) '
|
||||
'(This option is required when setting encryption type '
|
||||
'of a volume. Consider using other encryption options '
|
||||
'such as: "--encryption-cipher", "--encryption-key-size" '
|
||||
'and "--encryption-control-location")'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--encryption-cipher',
|
||||
metavar='<cipher>',
|
||||
help=_(
|
||||
'Set the encryption algorithm or mode for this '
|
||||
'volume type (e.g "aes-xts-plain64") (admin only)'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--encryption-key-size',
|
||||
metavar='<key-size>',
|
||||
type=int,
|
||||
help=_(
|
||||
'Set the size of the encryption key of this '
|
||||
'volume type (e.g "128" or "256") (admin only)'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--encryption-control-location',
|
||||
metavar='<control-location>',
|
||||
choices=['front-end', 'back-end'],
|
||||
help=_(
|
||||
'Set the notional service where the encryption is '
|
||||
'performed ("front-end" or "back-end") (admin only) '
|
||||
'(The default value for this option is "front-end" '
|
||||
'when setting encryption type of a volume. Consider '
|
||||
'using other encryption options such as: '
|
||||
'"--encryption-cipher", "--encryption-key-size" and '
|
||||
'"--encryption-provider")'
|
||||
),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
volume_type = volume_client.volume_types.create(parsed_args.name)
|
||||
volume_type._info.pop('extra_specs')
|
||||
if parsed_args.property:
|
||||
result = volume_type.set_keys(parsed_args.property)
|
||||
volume_type._info.update(
|
||||
{'properties': format_columns.DictColumn(result)}
|
||||
)
|
||||
if (
|
||||
parsed_args.encryption_provider
|
||||
or parsed_args.encryption_cipher
|
||||
or parsed_args.encryption_key_size
|
||||
or parsed_args.encryption_control_location
|
||||
):
|
||||
try:
|
||||
# create new encryption
|
||||
encryption = _create_encryption_type(
|
||||
volume_client, volume_type, parsed_args
|
||||
)
|
||||
except Exception as e:
|
||||
LOG.error(
|
||||
_(
|
||||
"Failed to set encryption information for this "
|
||||
"volume type: %s"
|
||||
),
|
||||
e,
|
||||
)
|
||||
# add encryption info in result
|
||||
encryption._info.pop("volume_type_id", None)
|
||||
volume_type._info.update(
|
||||
{'encryption': format_columns.DictColumn(encryption._info)}
|
||||
)
|
||||
volume_type._info.pop("os-volume-type-access:is_public", None)
|
||||
|
||||
return zip(*sorted(volume_type._info.items()))
|
||||
|
||||
|
||||
class DeleteVolumeType(command.Command):
|
||||
_description = _("Delete volume type(s)")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'volume_types',
|
||||
metavar='<volume-type>',
|
||||
nargs='+',
|
||||
help=_('Volume type(s) to delete (name or ID)'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
result = 0
|
||||
|
||||
for volume_type in parsed_args.volume_types:
|
||||
try:
|
||||
vol_type = utils.find_resource(
|
||||
volume_client.volume_types, volume_type
|
||||
)
|
||||
|
||||
volume_client.volume_types.delete(vol_type)
|
||||
except Exception as e:
|
||||
result += 1
|
||||
LOG.error(
|
||||
_(
|
||||
"Failed to delete volume type with "
|
||||
"name or ID '%(volume_type)s': %(e)s"
|
||||
)
|
||||
% {'volume_type': volume_type, 'e': e}
|
||||
)
|
||||
|
||||
if result > 0:
|
||||
total = len(parsed_args.volume_types)
|
||||
msg = _(
|
||||
"%(result)s of %(total)s volume types failed to delete."
|
||||
) % {'result': result, 'total': total}
|
||||
raise exceptions.CommandError(msg)
|
||||
|
||||
|
||||
class ListVolumeType(command.Lister):
|
||||
_description = _("List volume types")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--long',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('List additional fields in output'),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--encryption-type",
|
||||
action="store_true",
|
||||
help=_(
|
||||
"Display encryption information for each volume type "
|
||||
"(admin only)"
|
||||
),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
if parsed_args.long:
|
||||
columns = ['ID', 'Name', 'Is Public', 'Extra Specs']
|
||||
column_headers = ['ID', 'Name', 'Is Public', 'Properties']
|
||||
else:
|
||||
columns = ['ID', 'Name', 'Is Public']
|
||||
column_headers = ['ID', 'Name', 'Is Public']
|
||||
data = volume_client.volume_types.list()
|
||||
|
||||
formatters = {'Extra Specs': format_columns.DictColumn}
|
||||
|
||||
if parsed_args.encryption_type:
|
||||
encryption = {}
|
||||
for d in volume_client.volume_encryption_types.list():
|
||||
volume_type_id = d._info['volume_type_id']
|
||||
# remove some redundant information
|
||||
del_key = [
|
||||
'deleted',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
'volume_type_id',
|
||||
]
|
||||
for key in del_key:
|
||||
d._info.pop(key, None)
|
||||
# save the encryption information with their volume type ID
|
||||
encryption[volume_type_id] = d._info
|
||||
# We need to get volume type ID, then show encryption
|
||||
# information according to the ID, so use "id" to keep
|
||||
# difference to the real "ID" column.
|
||||
columns += ['id']
|
||||
column_headers += ['Encryption']
|
||||
|
||||
_EncryptionInfoColumn = functools.partial(
|
||||
EncryptionInfoColumn, encryption_data=encryption
|
||||
)
|
||||
formatters['id'] = _EncryptionInfoColumn
|
||||
|
||||
return (
|
||||
column_headers,
|
||||
(
|
||||
utils.get_item_properties(
|
||||
s,
|
||||
columns,
|
||||
formatters=formatters,
|
||||
)
|
||||
for s in data
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class SetVolumeType(command.Command):
|
||||
_description = _("Set volume type properties")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'volume_type',
|
||||
metavar='<volume-type>',
|
||||
help=_('Volume type to modify (name or ID)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--property',
|
||||
metavar='<key=value>',
|
||||
action=parseractions.KeyValueAction,
|
||||
help=_(
|
||||
'Set a property on this volume type '
|
||||
'(repeat option to set multiple properties)'
|
||||
),
|
||||
)
|
||||
# TODO(Huanxuan Ao): Add choices for each "--encryption-*" option.
|
||||
parser.add_argument(
|
||||
'--encryption-provider',
|
||||
metavar='<provider>',
|
||||
help=_(
|
||||
'Set the encryption provider format for '
|
||||
'this volume type (e.g "luks" or "plain") (admin only) '
|
||||
'(This option is required when setting encryption type '
|
||||
'of a volume. Consider using other encryption options '
|
||||
'such as: "--encryption-cipher", "--encryption-key-size" '
|
||||
'and "--encryption-control-location")'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--encryption-cipher',
|
||||
metavar='<cipher>',
|
||||
help=_(
|
||||
'Set the encryption algorithm or mode for this '
|
||||
'volume type (e.g "aes-xts-plain64") (admin only)'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--encryption-key-size',
|
||||
metavar='<key-size>',
|
||||
type=int,
|
||||
help=_(
|
||||
'Set the size of the encryption key of this '
|
||||
'volume type (e.g "128" or "256") (admin only)'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--encryption-control-location',
|
||||
metavar='<control-location>',
|
||||
choices=['front-end', 'back-end'],
|
||||
help=_(
|
||||
'Set the notional service where the encryption is '
|
||||
'performed ("front-end" or "back-end") (admin only) '
|
||||
'(The default value for this option is "front-end" '
|
||||
'when setting encryption type of a volume. Consider '
|
||||
'using other encryption options such as: '
|
||||
'"--encryption-cipher", "--encryption-key-size" and '
|
||||
'"--encryption-provider")'
|
||||
),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
volume_type = utils.find_resource(
|
||||
volume_client.volume_types, parsed_args.volume_type
|
||||
)
|
||||
|
||||
result = 0
|
||||
if parsed_args.property:
|
||||
try:
|
||||
volume_type.set_keys(parsed_args.property)
|
||||
except Exception as e:
|
||||
LOG.error(_("Failed to set volume type property: %s"), e)
|
||||
result += 1
|
||||
|
||||
if (
|
||||
parsed_args.encryption_provider
|
||||
or parsed_args.encryption_cipher
|
||||
or parsed_args.encryption_key_size
|
||||
or parsed_args.encryption_control_location
|
||||
):
|
||||
try:
|
||||
_create_encryption_type(
|
||||
volume_client, volume_type, parsed_args
|
||||
)
|
||||
except Exception as e:
|
||||
LOG.error(
|
||||
_(
|
||||
"Failed to set encryption information for this "
|
||||
"volume type: %s"
|
||||
),
|
||||
e,
|
||||
)
|
||||
result += 1
|
||||
|
||||
if result > 0:
|
||||
raise exceptions.CommandError(
|
||||
_("Command Failed: One or more of the operations failed")
|
||||
)
|
||||
|
||||
|
||||
class ShowVolumeType(command.ShowOne):
|
||||
_description = _("Display volume type details")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
"volume_type",
|
||||
metavar="<volume-type>",
|
||||
help=_("Volume type to display (name or ID)"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--encryption-type",
|
||||
action="store_true",
|
||||
help=_(
|
||||
"Display encryption information of this volume type "
|
||||
"(admin only)"
|
||||
),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
volume_type = utils.find_resource(
|
||||
volume_client.volume_types, parsed_args.volume_type
|
||||
)
|
||||
properties = format_columns.DictColumn(
|
||||
volume_type._info.pop('extra_specs')
|
||||
)
|
||||
volume_type._info.update({'properties': properties})
|
||||
if parsed_args.encryption_type:
|
||||
# show encryption type information for this volume type
|
||||
try:
|
||||
encryption = volume_client.volume_encryption_types.get(
|
||||
volume_type.id
|
||||
)
|
||||
encryption._info.pop("volume_type_id", None)
|
||||
volume_type._info.update(
|
||||
{'encryption': format_columns.DictColumn(encryption._info)}
|
||||
)
|
||||
except Exception as e:
|
||||
LOG.error(
|
||||
_(
|
||||
"Failed to display the encryption information "
|
||||
"of this volume type: %s"
|
||||
),
|
||||
e,
|
||||
)
|
||||
volume_type._info.pop("os-volume-type-access:is_public", None)
|
||||
return zip(*sorted(volume_type._info.items()))
|
||||
|
||||
|
||||
class UnsetVolumeType(command.Command):
|
||||
_description = _("Unset volume type properties")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'volume_type',
|
||||
metavar='<volume-type>',
|
||||
help=_('Volume type to modify (name or ID)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--property',
|
||||
metavar='<key>',
|
||||
action='append',
|
||||
help=_(
|
||||
'Remove a property from this volume type '
|
||||
'(repeat option to remove multiple properties)'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--encryption-type",
|
||||
action="store_true",
|
||||
help=_(
|
||||
"Remove the encryption type for this volume type (admin only)"
|
||||
),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
volume_client = self.app.client_manager.volume
|
||||
volume_type = utils.find_resource(
|
||||
volume_client.volume_types,
|
||||
parsed_args.volume_type,
|
||||
)
|
||||
|
||||
result = 0
|
||||
if parsed_args.property:
|
||||
try:
|
||||
volume_type.unset_keys(parsed_args.property)
|
||||
except Exception as e:
|
||||
LOG.error(_("Failed to unset volume type property: %s"), e)
|
||||
result += 1
|
||||
if parsed_args.encryption_type:
|
||||
try:
|
||||
volume_client.volume_encryption_types.delete(volume_type)
|
||||
except Exception as e:
|
||||
LOG.error(
|
||||
_(
|
||||
"Failed to remove the encryption type for this "
|
||||
"volume type: %s"
|
||||
),
|
||||
e,
|
||||
)
|
||||
result += 1
|
||||
|
||||
if result > 0:
|
||||
raise exceptions.CommandError(
|
||||
_("Command Failed: One or more of the operations failed")
|
||||
)
|
@ -0,0 +1,35 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
Support for the Block Storage (Cinder) v1 API has been officially removed
|
||||
as it had been broken for some time. If you haven't noticed then you likely
|
||||
don't need to do anything. However, in the unlikely event that your cloud
|
||||
is using the Block Storage v1 API - or incorrectly advertises the Block
|
||||
Storage v1 API - consider overriding the API version to use v2 as this
|
||||
behaves very similarly. It may also be necessary to set an endpoint
|
||||
override for the Block Storage API if your clouds service catalog is not
|
||||
configured correctly. For example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
example:
|
||||
regions:
|
||||
- name: regionOne
|
||||
values:
|
||||
block_storage_endpoint_override: 'https://blockstorage.api.cloud.example/'
|
||||
volume_api_version: 2
|
||||
|
||||
If using a public cloud provider, there may also be a profile already
|
||||
published that sets these. These are listed in the `Vendor Support`__
|
||||
doc. For example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
example:
|
||||
profile: rackspace
|
||||
|
||||
Alternatively, consider use versions of OSC < 3.19 and python-cinderclient
|
||||
< 5.0 (both Stein), since these were the last versions to fully support
|
||||
Cinder v1.
|
||||
|
||||
.. __: https://docs.openstack.org/openstacksdk/latest/user/config/vendor-support.html
|
47
setup.cfg
47
setup.cfg
@ -635,53 +635,6 @@ openstack.object_store.v1 =
|
||||
object_show = openstackclient.object.v1.object:ShowObject
|
||||
object_unset = openstackclient.object.v1.object:UnsetObject
|
||||
|
||||
openstack.volume.v1 =
|
||||
volume_create = openstackclient.volume.v1.volume:CreateVolume
|
||||
volume_delete = openstackclient.volume.v1.volume:DeleteVolume
|
||||
volume_list = openstackclient.volume.v1.volume:ListVolume
|
||||
volume_migrate = openstackclient.volume.v1.volume:MigrateVolume
|
||||
volume_set = openstackclient.volume.v1.volume:SetVolume
|
||||
volume_show = openstackclient.volume.v1.volume:ShowVolume
|
||||
volume_unset = openstackclient.volume.v1.volume:UnsetVolume
|
||||
|
||||
volume_backup_create = openstackclient.volume.v1.volume_backup:CreateVolumeBackup
|
||||
volume_backup_delete = openstackclient.volume.v1.volume_backup:DeleteVolumeBackup
|
||||
volume_backup_list = openstackclient.volume.v1.volume_backup:ListVolumeBackup
|
||||
volume_backup_restore = openstackclient.volume.v1.volume_backup:RestoreVolumeBackup
|
||||
volume_backup_show = openstackclient.volume.v1.volume_backup:ShowVolumeBackup
|
||||
|
||||
volume_snapshot_create = openstackclient.volume.v1.volume_snapshot:CreateVolumeSnapshot
|
||||
volume_snapshot_delete = openstackclient.volume.v1.volume_snapshot:DeleteVolumeSnapshot
|
||||
volume_snapshot_list = openstackclient.volume.v1.volume_snapshot:ListVolumeSnapshot
|
||||
volume_snapshot_set = openstackclient.volume.v1.volume_snapshot:SetVolumeSnapshot
|
||||
volume_snapshot_show = openstackclient.volume.v1.volume_snapshot:ShowVolumeSnapshot
|
||||
volume_snapshot_unset = openstackclient.volume.v1.volume_snapshot:UnsetVolumeSnapshot
|
||||
|
||||
volume_type_create = openstackclient.volume.v1.volume_type:CreateVolumeType
|
||||
volume_type_delete = openstackclient.volume.v1.volume_type:DeleteVolumeType
|
||||
volume_type_list = openstackclient.volume.v1.volume_type:ListVolumeType
|
||||
volume_type_set = openstackclient.volume.v1.volume_type:SetVolumeType
|
||||
volume_type_show = openstackclient.volume.v1.volume_type:ShowVolumeType
|
||||
volume_type_unset = openstackclient.volume.v1.volume_type:UnsetVolumeType
|
||||
|
||||
volume_qos_associate = openstackclient.volume.v1.qos_specs:AssociateQos
|
||||
volume_qos_create = openstackclient.volume.v1.qos_specs:CreateQos
|
||||
volume_qos_delete = openstackclient.volume.v1.qos_specs:DeleteQos
|
||||
volume_qos_disassociate = openstackclient.volume.v1.qos_specs:DisassociateQos
|
||||
volume_qos_list = openstackclient.volume.v1.qos_specs:ListQos
|
||||
volume_qos_set = openstackclient.volume.v1.qos_specs:SetQos
|
||||
volume_qos_show = openstackclient.volume.v1.qos_specs:ShowQos
|
||||
volume_qos_unset = openstackclient.volume.v1.qos_specs:UnsetQos
|
||||
|
||||
volume_service_list = openstackclient.volume.v1.service:ListService
|
||||
volume_service_set = openstackclient.volume.v1.service:SetService
|
||||
|
||||
volume_transfer_request_accept = openstackclient.volume.v1.volume_transfer_request:AcceptTransferRequest
|
||||
volume_transfer_request_create = openstackclient.volume.v1.volume_transfer_request:CreateTransferRequest
|
||||
volume_transfer_request_delete = openstackclient.volume.v1.volume_transfer_request:DeleteTransferRequest
|
||||
volume_transfer_request_list = openstackclient.volume.v1.volume_transfer_request:ListTransferRequest
|
||||
volume_transfer_request_show = openstackclient.volume.v1.volume_transfer_request:ShowTransferRequest
|
||||
|
||||
openstack.volume.v2 =
|
||||
consistency_group_add_volume = openstackclient.volume.v2.consistency_group:AddVolumeToConsistencyGroup
|
||||
consistency_group_create = openstackclient.volume.v2.consistency_group:CreateConsistencyGroup
|
||||
|
Loading…
x
Reference in New Issue
Block a user