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