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:
Stephen Finucane 2025-03-13 11:39:24 +00:00
parent 3eb063d4f7
commit e6be9a3edf
37 changed files with 47 additions and 8021 deletions

View File

@ -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::

View File

@ -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

View File

@ -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 *

View File

@ -2,7 +2,7 @@
volume backup
=============
Block Storage v1, v2, v3
Block Storage v2, v3
.. autoprogram-cliff:: openstack.volume.v3
:command: volume backup *

View File

@ -2,7 +2,7 @@
volume qos
==========
Block Storage v1, v2, v3
Block Storage v2, v3
.. autoprogram-cliff:: openstack.volume.v3
:command: volume qos *

View File

@ -2,7 +2,7 @@
volume service
==============
Block Storage v1, v2, v3
Block Storage v2, v3
.. autoprogram-cliff:: openstack.volume.v3
:command: volume service *

View File

@ -2,7 +2,7 @@
volume snapshot
===============
Block Storage v1, v2, v3
Block Storage v2, v3
.. autoprogram-cliff:: openstack.volume.v3
:command: volume snapshot *

View File

@ -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 *

View File

@ -2,7 +2,7 @@
volume type
===========
Block Storage v1, v2, v3
Block Storage v2, v3
.. autoprogram-cliff:: openstack.volume.v3
:command: volume type *

View File

@ -2,7 +2,7 @@
volume
======
Block Storage v1, v2
Block Storage v2, v3
.. autoprogram-cliff:: openstack.volume.v3
:command: volume create

View File

@ -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)

View File

@ -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

View File

@ -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'])

View File

@ -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"])

View File

@ -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'])

View File

@ -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)

View File

@ -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)

View File

@ -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,
)

View File

@ -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)

View File

@ -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)

View File

@ -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),
)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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
)

View File

@ -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
)

View File

@ -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,
)

View File

@ -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()))

View File

@ -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,
)

View File

@ -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()))

View File

@ -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")
)

View File

@ -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

View File

@ -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