volume: Migrate 'volume create' to SDK
We need a shim for consistency group support, which we may eventually port to SDK but have not yet. Otherwise, this is rather straightforward. Change-Id: Ic880b7a64cde2148c266d549c4768c669ba3f501 Signed-off-by: Stephen Finucane <stephenfin@redhat.com> Depends-on: https://review.opendev.org/c/openstack/openstacksdk/+/943800
This commit is contained in:
@@ -64,7 +64,7 @@ def list_security_groups(compute_client, all_projects=None):
|
||||
|
||||
|
||||
def find_security_group(compute_client, name_or_id):
|
||||
"""Find the name for a given security group name or ID
|
||||
"""Find the security group for a given name or ID
|
||||
|
||||
https://docs.openstack.org/api-ref/compute/#show-security-group-details
|
||||
|
||||
@@ -240,7 +240,7 @@ def list_networks(compute_client):
|
||||
|
||||
|
||||
def find_network(compute_client, name_or_id):
|
||||
"""Find the ID for a given network name or ID
|
||||
"""Find the network for a given name or ID
|
||||
|
||||
https://docs.openstack.org/api-ref/compute/#show-network-details
|
||||
|
||||
|
60
openstackclient/api/volume_v2.py
Normal file
60
openstackclient/api/volume_v2.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# 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 v2 API Library
|
||||
|
||||
A collection of wrappers for deprecated Block Storage v2 APIs that are not
|
||||
intentionally supported by SDK.
|
||||
"""
|
||||
|
||||
import http
|
||||
|
||||
from openstack import exceptions as sdk_exceptions
|
||||
from osc_lib import exceptions
|
||||
|
||||
|
||||
# consistency groups
|
||||
|
||||
|
||||
def find_consistency_group(compute_client, name_or_id):
|
||||
"""Find the consistency group for a given name or ID
|
||||
|
||||
https://docs.openstack.org/api-ref/block-storage/v3/#show-a-consistency-group-s-details
|
||||
|
||||
:param volume_client: A volume client
|
||||
:param name_or_id: The name or ID of the consistency group to look up
|
||||
:returns: A consistency group object
|
||||
:raises exception.NotFound: If a matching consistency group could not be
|
||||
found or more than one match was found
|
||||
"""
|
||||
response = compute_client.get(f'/consistencygroups/{name_or_id}')
|
||||
if response.status_code != http.HTTPStatus.NOT_FOUND:
|
||||
# there might be other, non-404 errors
|
||||
sdk_exceptions.raise_from_response(response)
|
||||
return response.json()['consistencygroup']
|
||||
|
||||
response = compute_client.get('/consistencygroups')
|
||||
sdk_exceptions.raise_from_response(response)
|
||||
found = None
|
||||
consistency_groups = response.json()['consistencygroups']
|
||||
for consistency_group in consistency_groups:
|
||||
if consistency_group['name'] == name_or_id:
|
||||
if found:
|
||||
raise exceptions.NotFound(
|
||||
f'multiple matches found for {name_or_id}'
|
||||
)
|
||||
found = consistency_group
|
||||
|
||||
if not found:
|
||||
raise exceptions.NotFound(f'{name_or_id} not found')
|
||||
|
||||
return found
|
60
openstackclient/api/volume_v3.py
Normal file
60
openstackclient/api/volume_v3.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# 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 v3 API Library
|
||||
|
||||
A collection of wrappers for deprecated Block Storage v3 APIs that are not
|
||||
intentionally supported by SDK.
|
||||
"""
|
||||
|
||||
import http
|
||||
|
||||
from openstack import exceptions as sdk_exceptions
|
||||
from osc_lib import exceptions
|
||||
|
||||
|
||||
# consistency groups
|
||||
|
||||
|
||||
def find_consistency_group(compute_client, name_or_id):
|
||||
"""Find the consistency group for a given name or ID
|
||||
|
||||
https://docs.openstack.org/api-ref/block-storage/v3/#show-a-consistency-group-s-details
|
||||
|
||||
:param volume_client: A volume client
|
||||
:param name_or_id: The name or ID of the consistency group to look up
|
||||
:returns: A consistency group object
|
||||
:raises exception.NotFound: If a matching consistency group could not be
|
||||
found or more than one match was found
|
||||
"""
|
||||
response = compute_client.get(f'/consistencygroups/{name_or_id}')
|
||||
if response.status_code != http.HTTPStatus.NOT_FOUND:
|
||||
# there might be other, non-404 errors
|
||||
sdk_exceptions.raise_from_response(response)
|
||||
return response.json()['consistencygroup']
|
||||
|
||||
response = compute_client.get('/consistencygroups')
|
||||
sdk_exceptions.raise_from_response(response)
|
||||
found = None
|
||||
consistency_groups = response.json()['consistencygroups']
|
||||
for consistency_group in consistency_groups:
|
||||
if consistency_group['name'] == name_or_id:
|
||||
if found:
|
||||
raise exceptions.NotFound(
|
||||
f'multiple matches found for {name_or_id}'
|
||||
)
|
||||
found = consistency_group
|
||||
|
||||
if not found:
|
||||
raise exceptions.NotFound(f'{name_or_id} not found')
|
||||
|
||||
return found
|
@@ -124,7 +124,7 @@ class VolumeTests(common.BaseVolumeTests):
|
||||
cmd_output["properties"],
|
||||
)
|
||||
self.assertEqual(
|
||||
'false',
|
||||
False,
|
||||
cmd_output["bootable"],
|
||||
)
|
||||
self.wait_for_status("volume", name, "available")
|
||||
|
124
openstackclient/tests/unit/api/test_volume_v2.py
Normal file
124
openstackclient/tests/unit/api/test_volume_v2.py
Normal file
@@ -0,0 +1,124 @@
|
||||
# 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 v2 API Library Tests"""
|
||||
|
||||
import http
|
||||
from unittest import mock
|
||||
import uuid
|
||||
|
||||
from openstack.block_storage.v2 import _proxy
|
||||
from osc_lib import exceptions as osc_lib_exceptions
|
||||
|
||||
from openstackclient.api import volume_v2 as volume
|
||||
from openstackclient.tests.unit import fakes
|
||||
from openstackclient.tests.unit import utils
|
||||
|
||||
|
||||
class TestConsistencyGroup(utils.TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.volume_sdk_client = mock.Mock(_proxy.Proxy)
|
||||
|
||||
def test_find_consistency_group_by_id(self):
|
||||
cg_id = uuid.uuid4().hex
|
||||
cg_name = 'name-' + uuid.uuid4().hex
|
||||
data = {
|
||||
'consistencygroup': {
|
||||
'id': cg_id,
|
||||
'name': cg_name,
|
||||
'status': 'available',
|
||||
'availability_zone': 'az1',
|
||||
'created_at': '2015-09-16T09:28:52.000000',
|
||||
'description': 'description-' + uuid.uuid4().hex,
|
||||
'volume_types': ['123456'],
|
||||
}
|
||||
}
|
||||
self.volume_sdk_client.get.side_effect = [
|
||||
fakes.FakeResponse(data=data),
|
||||
]
|
||||
|
||||
result = volume.find_consistency_group(self.volume_sdk_client, cg_id)
|
||||
|
||||
self.volume_sdk_client.get.assert_has_calls(
|
||||
[
|
||||
mock.call(f'/consistencygroups/{cg_id}'),
|
||||
]
|
||||
)
|
||||
self.assertEqual(data['consistencygroup'], result)
|
||||
|
||||
def test_find_consistency_group_by_name(self):
|
||||
cg_id = uuid.uuid4().hex
|
||||
cg_name = 'name-' + uuid.uuid4().hex
|
||||
data = {
|
||||
'consistencygroups': [
|
||||
{
|
||||
'id': cg_id,
|
||||
'name': cg_name,
|
||||
}
|
||||
],
|
||||
}
|
||||
self.volume_sdk_client.get.side_effect = [
|
||||
fakes.FakeResponse(status_code=http.HTTPStatus.NOT_FOUND),
|
||||
fakes.FakeResponse(data=data),
|
||||
]
|
||||
|
||||
result = volume.find_consistency_group(self.volume_sdk_client, cg_name)
|
||||
|
||||
self.volume_sdk_client.get.assert_has_calls(
|
||||
[
|
||||
mock.call(f'/consistencygroups/{cg_name}'),
|
||||
mock.call('/consistencygroups'),
|
||||
]
|
||||
)
|
||||
self.assertEqual(data['consistencygroups'][0], result)
|
||||
|
||||
def test_find_consistency_group_not_found(self):
|
||||
data = {'consistencygroups': []}
|
||||
self.volume_sdk_client.get.side_effect = [
|
||||
fakes.FakeResponse(status_code=http.HTTPStatus.NOT_FOUND),
|
||||
fakes.FakeResponse(data=data),
|
||||
]
|
||||
self.assertRaises(
|
||||
osc_lib_exceptions.NotFound,
|
||||
volume.find_consistency_group,
|
||||
self.volume_sdk_client,
|
||||
'invalid-cg',
|
||||
)
|
||||
|
||||
def test_find_consistency_group_by_name_duplicate(self):
|
||||
cg_name = 'name-' + uuid.uuid4().hex
|
||||
data = {
|
||||
'consistencygroups': [
|
||||
{
|
||||
'id': uuid.uuid4().hex,
|
||||
'name': cg_name,
|
||||
},
|
||||
{
|
||||
'id': uuid.uuid4().hex,
|
||||
'name': cg_name,
|
||||
},
|
||||
],
|
||||
}
|
||||
self.volume_sdk_client.get.side_effect = [
|
||||
fakes.FakeResponse(status_code=http.HTTPStatus.NOT_FOUND),
|
||||
fakes.FakeResponse(data=data),
|
||||
]
|
||||
|
||||
self.assertRaises(
|
||||
osc_lib_exceptions.NotFound,
|
||||
volume.find_consistency_group,
|
||||
self.volume_sdk_client,
|
||||
cg_name,
|
||||
)
|
124
openstackclient/tests/unit/api/test_volume_v3.py
Normal file
124
openstackclient/tests/unit/api/test_volume_v3.py
Normal file
@@ -0,0 +1,124 @@
|
||||
# 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 v3 API Library Tests"""
|
||||
|
||||
import http
|
||||
from unittest import mock
|
||||
import uuid
|
||||
|
||||
from openstack.block_storage.v3 import _proxy
|
||||
from osc_lib import exceptions as osc_lib_exceptions
|
||||
|
||||
from openstackclient.api import volume_v3 as volume
|
||||
from openstackclient.tests.unit import fakes
|
||||
from openstackclient.tests.unit import utils
|
||||
|
||||
|
||||
class TestConsistencyGroup(utils.TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.volume_sdk_client = mock.Mock(_proxy.Proxy)
|
||||
|
||||
def test_find_consistency_group_by_id(self):
|
||||
cg_id = uuid.uuid4().hex
|
||||
cg_name = 'name-' + uuid.uuid4().hex
|
||||
data = {
|
||||
'consistencygroup': {
|
||||
'id': cg_id,
|
||||
'name': cg_name,
|
||||
'status': 'available',
|
||||
'availability_zone': 'az1',
|
||||
'created_at': '2015-09-16T09:28:52.000000',
|
||||
'description': 'description-' + uuid.uuid4().hex,
|
||||
'volume_types': ['123456'],
|
||||
}
|
||||
}
|
||||
self.volume_sdk_client.get.side_effect = [
|
||||
fakes.FakeResponse(data=data),
|
||||
]
|
||||
|
||||
result = volume.find_consistency_group(self.volume_sdk_client, cg_id)
|
||||
|
||||
self.volume_sdk_client.get.assert_has_calls(
|
||||
[
|
||||
mock.call(f'/consistencygroups/{cg_id}'),
|
||||
]
|
||||
)
|
||||
self.assertEqual(data['consistencygroup'], result)
|
||||
|
||||
def test_find_consistency_group_by_name(self):
|
||||
cg_id = uuid.uuid4().hex
|
||||
cg_name = 'name-' + uuid.uuid4().hex
|
||||
data = {
|
||||
'consistencygroups': [
|
||||
{
|
||||
'id': cg_id,
|
||||
'name': cg_name,
|
||||
}
|
||||
],
|
||||
}
|
||||
self.volume_sdk_client.get.side_effect = [
|
||||
fakes.FakeResponse(status_code=http.HTTPStatus.NOT_FOUND),
|
||||
fakes.FakeResponse(data=data),
|
||||
]
|
||||
|
||||
result = volume.find_consistency_group(self.volume_sdk_client, cg_name)
|
||||
|
||||
self.volume_sdk_client.get.assert_has_calls(
|
||||
[
|
||||
mock.call(f'/consistencygroups/{cg_name}'),
|
||||
mock.call('/consistencygroups'),
|
||||
]
|
||||
)
|
||||
self.assertEqual(data['consistencygroups'][0], result)
|
||||
|
||||
def test_find_consistency_group_not_found(self):
|
||||
data = {'consistencygroups': []}
|
||||
self.volume_sdk_client.get.side_effect = [
|
||||
fakes.FakeResponse(status_code=http.HTTPStatus.NOT_FOUND),
|
||||
fakes.FakeResponse(data=data),
|
||||
]
|
||||
self.assertRaises(
|
||||
osc_lib_exceptions.NotFound,
|
||||
volume.find_consistency_group,
|
||||
self.volume_sdk_client,
|
||||
'invalid-cg',
|
||||
)
|
||||
|
||||
def test_find_consistency_group_by_name_duplicate(self):
|
||||
cg_name = 'name-' + uuid.uuid4().hex
|
||||
data = {
|
||||
'consistencygroups': [
|
||||
{
|
||||
'id': uuid.uuid4().hex,
|
||||
'name': cg_name,
|
||||
},
|
||||
{
|
||||
'id': uuid.uuid4().hex,
|
||||
'name': cg_name,
|
||||
},
|
||||
],
|
||||
}
|
||||
self.volume_sdk_client.get.side_effect = [
|
||||
fakes.FakeResponse(status_code=http.HTTPStatus.NOT_FOUND),
|
||||
fakes.FakeResponse(data=data),
|
||||
]
|
||||
|
||||
self.assertRaises(
|
||||
osc_lib_exceptions.NotFound,
|
||||
volume.find_consistency_group,
|
||||
self.volume_sdk_client,
|
||||
cg_name,
|
||||
)
|
@@ -13,6 +13,7 @@
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from openstack.block_storage.v2 import snapshot as _snapshot
|
||||
from openstack.block_storage.v2 import volume as _volume
|
||||
from openstack import exceptions as sdk_exceptions
|
||||
from openstack.test import fakes as sdk_fakes
|
||||
@@ -20,6 +21,7 @@ from osc_lib.cli import format_columns
|
||||
from osc_lib import exceptions
|
||||
from osc_lib import utils
|
||||
|
||||
from openstackclient.api import volume_v2
|
||||
from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
|
||||
from openstackclient.tests.unit.image.v2 import fakes as image_fakes
|
||||
from openstackclient.tests.unit import utils as test_utils
|
||||
@@ -50,129 +52,156 @@ class TestVolume(volume_fakes.TestVolume):
|
||||
self.consistencygroups_mock.reset_mock()
|
||||
|
||||
|
||||
class TestVolumeCreate(TestVolume):
|
||||
project = identity_fakes.FakeProject.create_one_project()
|
||||
user = identity_fakes.FakeUser.create_one_user()
|
||||
|
||||
class TestVolumeCreate(volume_fakes.TestVolume):
|
||||
columns = (
|
||||
'attachments',
|
||||
'availability_zone',
|
||||
'bootable',
|
||||
'consistencygroup_id',
|
||||
'created_at',
|
||||
'description',
|
||||
'encrypted',
|
||||
'id',
|
||||
'multiattach',
|
||||
'name',
|
||||
'os-vol-host-attr:host',
|
||||
'os-vol-mig-status-attr:migstat',
|
||||
'os-vol-mig-status-attr:name_id',
|
||||
'os-vol-tenant-attr:tenant_id',
|
||||
'os-volume-replication:driver_data',
|
||||
'os-volume-replication:extended_status',
|
||||
'properties',
|
||||
'replication_status',
|
||||
'size',
|
||||
'snapshot_id',
|
||||
'source_volid',
|
||||
'status',
|
||||
'type',
|
||||
'updated_at',
|
||||
'user_id',
|
||||
'volume_image_metadata',
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.new_volume = volume_fakes.create_one_volume()
|
||||
self.volumes_mock.create.return_value = self.new_volume
|
||||
self.volume = sdk_fakes.generate_fake_resource(_volume.Volume)
|
||||
self.volume_sdk_client.create_volume.return_value = self.volume
|
||||
|
||||
self.datalist = (
|
||||
self.new_volume.attachments,
|
||||
self.new_volume.availability_zone,
|
||||
self.new_volume.bootable,
|
||||
self.new_volume.description,
|
||||
self.new_volume.id,
|
||||
self.new_volume.name,
|
||||
format_columns.DictColumn(self.new_volume.metadata),
|
||||
self.new_volume.size,
|
||||
self.new_volume.snapshot_id,
|
||||
self.new_volume.status,
|
||||
self.new_volume.volume_type,
|
||||
self.volume.attachments,
|
||||
self.volume.availability_zone,
|
||||
self.volume.is_bootable,
|
||||
self.volume.consistency_group_id,
|
||||
self.volume.created_at,
|
||||
self.volume.description,
|
||||
self.volume.is_encrypted,
|
||||
self.volume.id,
|
||||
self.volume.is_multiattach,
|
||||
self.volume.name,
|
||||
self.volume.host,
|
||||
self.volume.migration_status,
|
||||
self.volume.migration_id,
|
||||
self.volume.project_id,
|
||||
self.volume.replication_driver_data,
|
||||
self.volume.extended_replication_status,
|
||||
format_columns.DictColumn(self.volume.metadata),
|
||||
self.volume.replication_status,
|
||||
self.volume.size,
|
||||
self.volume.snapshot_id,
|
||||
self.volume.source_volume_id,
|
||||
self.volume.status,
|
||||
self.volume.volume_type,
|
||||
self.volume.updated_at,
|
||||
self.volume.user_id,
|
||||
self.volume.volume_image_metadata,
|
||||
)
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = volume.CreateVolume(self.app, None)
|
||||
|
||||
def test_volume_create_min_options(self):
|
||||
arglist = [
|
||||
'--size',
|
||||
str(self.new_volume.size),
|
||||
str(self.volume.size),
|
||||
]
|
||||
verifylist = [
|
||||
('size', self.new_volume.size),
|
||||
('size', self.volume.size),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# In base command class ShowOne in cliff, abstract method take_action()
|
||||
# returns a two-part tuple with a tuple of column names and a tuple of
|
||||
# data to be shown.
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.volumes_mock.create.assert_called_with(
|
||||
size=self.new_volume.size,
|
||||
self.volume_sdk_client.create_volume.assert_called_with(
|
||||
size=self.volume.size,
|
||||
snapshot_id=None,
|
||||
name=None,
|
||||
description=None,
|
||||
volume_type=None,
|
||||
availability_zone=None,
|
||||
metadata=None,
|
||||
imageRef=None,
|
||||
source_volid=None,
|
||||
consistencygroup_id=None,
|
||||
image_id=None,
|
||||
source_volume_id=None,
|
||||
consistency_group_id=None,
|
||||
scheduler_hints=None,
|
||||
)
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.datalist, data)
|
||||
self.assertEqual(self.datalist, data)
|
||||
|
||||
def test_volume_create_options(self):
|
||||
consistency_group = volume_fakes.create_one_consistency_group()
|
||||
self.consistencygroups_mock.get.return_value = consistency_group
|
||||
consistency_group_id = 'cg123'
|
||||
arglist = [
|
||||
'--size',
|
||||
str(self.new_volume.size),
|
||||
str(self.volume.size),
|
||||
'--description',
|
||||
self.new_volume.description,
|
||||
self.volume.description,
|
||||
'--type',
|
||||
self.new_volume.volume_type,
|
||||
self.volume.volume_type,
|
||||
'--availability-zone',
|
||||
self.new_volume.availability_zone,
|
||||
self.volume.availability_zone,
|
||||
'--consistency-group',
|
||||
consistency_group.id,
|
||||
consistency_group_id,
|
||||
'--hint',
|
||||
'k=v',
|
||||
self.new_volume.name,
|
||||
self.volume.name,
|
||||
]
|
||||
verifylist = [
|
||||
('size', self.new_volume.size),
|
||||
('description', self.new_volume.description),
|
||||
('type', self.new_volume.volume_type),
|
||||
('availability_zone', self.new_volume.availability_zone),
|
||||
('consistency_group', consistency_group.id),
|
||||
('size', self.volume.size),
|
||||
('description', self.volume.description),
|
||||
('type', self.volume.volume_type),
|
||||
('availability_zone', self.volume.availability_zone),
|
||||
('consistency_group', consistency_group_id),
|
||||
('hint', {'k': 'v'}),
|
||||
('name', self.new_volume.name),
|
||||
('name', self.volume.name),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# In base command class ShowOne in cliff, abstract method take_action()
|
||||
# returns a two-part tuple with a tuple of column names and a tuple of
|
||||
# data to be shown.
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
with mock.patch.object(
|
||||
volume_v2,
|
||||
'find_consistency_group',
|
||||
return_value={'id': consistency_group_id},
|
||||
) as mock_find_cg:
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.volumes_mock.create.assert_called_with(
|
||||
size=self.new_volume.size,
|
||||
self.volume_sdk_client.create_volume.assert_called_with(
|
||||
size=self.volume.size,
|
||||
snapshot_id=None,
|
||||
name=self.new_volume.name,
|
||||
description=self.new_volume.description,
|
||||
volume_type=self.new_volume.volume_type,
|
||||
availability_zone=self.new_volume.availability_zone,
|
||||
name=self.volume.name,
|
||||
description=self.volume.description,
|
||||
volume_type=self.volume.volume_type,
|
||||
availability_zone=self.volume.availability_zone,
|
||||
metadata=None,
|
||||
imageRef=None,
|
||||
source_volid=None,
|
||||
consistencygroup_id=consistency_group.id,
|
||||
image_id=None,
|
||||
source_volume_id=None,
|
||||
consistency_group_id=consistency_group_id,
|
||||
scheduler_hints={'k': 'v'},
|
||||
)
|
||||
mock_find_cg.assert_called_once_with(
|
||||
self.volume_sdk_client, consistency_group_id
|
||||
)
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.datalist, data)
|
||||
self.assertEqual(self.datalist, data)
|
||||
|
||||
def test_volume_create_properties(self):
|
||||
arglist = [
|
||||
@@ -181,39 +210,36 @@ class TestVolumeCreate(TestVolume):
|
||||
'--property',
|
||||
'Beta=b',
|
||||
'--size',
|
||||
str(self.new_volume.size),
|
||||
self.new_volume.name,
|
||||
str(self.volume.size),
|
||||
self.volume.name,
|
||||
]
|
||||
verifylist = [
|
||||
('properties', {'Alpha': 'a', 'Beta': 'b'}),
|
||||
('size', self.new_volume.size),
|
||||
('name', self.new_volume.name),
|
||||
('size', self.volume.size),
|
||||
('name', self.volume.name),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# In base command class ShowOne in cliff, abstract method take_action()
|
||||
# returns a two-part tuple with a tuple of column names and a tuple of
|
||||
# data to be shown.
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.volumes_mock.create.assert_called_with(
|
||||
size=self.new_volume.size,
|
||||
self.volume_sdk_client.create_volume.assert_called_with(
|
||||
size=self.volume.size,
|
||||
snapshot_id=None,
|
||||
name=self.new_volume.name,
|
||||
name=self.volume.name,
|
||||
description=None,
|
||||
volume_type=None,
|
||||
availability_zone=None,
|
||||
metadata={'Alpha': 'a', 'Beta': 'b'},
|
||||
imageRef=None,
|
||||
source_volid=None,
|
||||
consistencygroup_id=None,
|
||||
image_id=None,
|
||||
source_volume_id=None,
|
||||
consistency_group_id=None,
|
||||
scheduler_hints=None,
|
||||
)
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.datalist, data)
|
||||
self.assertEqual(self.datalist, data)
|
||||
|
||||
def test_volume_create_image_id(self):
|
||||
def test_volume_create_image(self):
|
||||
image = image_fakes.create_one_image()
|
||||
self.image_client.find_image.return_value = image
|
||||
|
||||
@@ -221,152 +247,111 @@ class TestVolumeCreate(TestVolume):
|
||||
'--image',
|
||||
image.id,
|
||||
'--size',
|
||||
str(self.new_volume.size),
|
||||
self.new_volume.name,
|
||||
str(self.volume.size),
|
||||
self.volume.name,
|
||||
]
|
||||
verifylist = [
|
||||
('image', image.id),
|
||||
('size', self.new_volume.size),
|
||||
('name', self.new_volume.name),
|
||||
('size', self.volume.size),
|
||||
('name', self.volume.name),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# In base command class ShowOne in cliff, abstract method take_action()
|
||||
# returns a two-part tuple with a tuple of column names and a tuple of
|
||||
# data to be shown.
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.volumes_mock.create.assert_called_with(
|
||||
size=self.new_volume.size,
|
||||
self.volume_sdk_client.create_volume.assert_called_with(
|
||||
size=self.volume.size,
|
||||
snapshot_id=None,
|
||||
name=self.new_volume.name,
|
||||
name=self.volume.name,
|
||||
description=None,
|
||||
volume_type=None,
|
||||
availability_zone=None,
|
||||
metadata=None,
|
||||
imageRef=image.id,
|
||||
source_volid=None,
|
||||
consistencygroup_id=None,
|
||||
image_id=image.id,
|
||||
source_volume_id=None,
|
||||
consistency_group_id=None,
|
||||
scheduler_hints=None,
|
||||
)
|
||||
self.image_client.find_image.assert_called_once_with(
|
||||
image.id, ignore_missing=False
|
||||
)
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.datalist, data)
|
||||
|
||||
def test_volume_create_image_name(self):
|
||||
image = image_fakes.create_one_image()
|
||||
self.image_client.find_image.return_value = image
|
||||
|
||||
arglist = [
|
||||
'--image',
|
||||
image.name,
|
||||
'--size',
|
||||
str(self.new_volume.size),
|
||||
self.new_volume.name,
|
||||
]
|
||||
verifylist = [
|
||||
('image', image.name),
|
||||
('size', self.new_volume.size),
|
||||
('name', self.new_volume.name),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# In base command class ShowOne in cliff, abstract method take_action()
|
||||
# returns a two-part tuple with a tuple of column names and a tuple of
|
||||
# data to be shown.
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.volumes_mock.create.assert_called_with(
|
||||
size=self.new_volume.size,
|
||||
snapshot_id=None,
|
||||
name=self.new_volume.name,
|
||||
description=None,
|
||||
volume_type=None,
|
||||
availability_zone=None,
|
||||
metadata=None,
|
||||
imageRef=image.id,
|
||||
source_volid=None,
|
||||
consistencygroup_id=None,
|
||||
scheduler_hints=None,
|
||||
)
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.datalist, data)
|
||||
self.assertEqual(self.datalist, data)
|
||||
|
||||
def test_volume_create_with_snapshot(self):
|
||||
snapshot = volume_fakes.create_one_snapshot()
|
||||
self.new_volume.snapshot_id = snapshot.id
|
||||
snapshot = sdk_fakes.generate_fake_resource(_snapshot.Snapshot)
|
||||
self.volume_sdk_client.find_snapshot.return_value = snapshot
|
||||
|
||||
arglist = [
|
||||
'--snapshot',
|
||||
self.new_volume.snapshot_id,
|
||||
self.new_volume.name,
|
||||
snapshot.id,
|
||||
self.volume.name,
|
||||
]
|
||||
verifylist = [
|
||||
('snapshot', self.new_volume.snapshot_id),
|
||||
('name', self.new_volume.name),
|
||||
('snapshot', snapshot.id),
|
||||
('name', self.volume.name),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.snapshots_mock.get.return_value = snapshot
|
||||
|
||||
# In base command class ShowOne in cliff, abstract method take_action()
|
||||
# returns a two-part tuple with a tuple of column names and a tuple of
|
||||
# data to be shown.
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.volumes_mock.create.assert_called_once_with(
|
||||
self.volume_sdk_client.create_volume.assert_called_with(
|
||||
size=snapshot.size,
|
||||
snapshot_id=snapshot.id,
|
||||
name=self.new_volume.name,
|
||||
name=self.volume.name,
|
||||
description=None,
|
||||
volume_type=None,
|
||||
availability_zone=None,
|
||||
metadata=None,
|
||||
imageRef=None,
|
||||
source_volid=None,
|
||||
consistencygroup_id=None,
|
||||
image_id=None,
|
||||
source_volume_id=None,
|
||||
consistency_group_id=None,
|
||||
scheduler_hints=None,
|
||||
)
|
||||
self.volume_sdk_client.find_snapshot.assert_called_once_with(
|
||||
snapshot.id, ignore_missing=False
|
||||
)
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.datalist, data)
|
||||
self.assertEqual(self.datalist, data)
|
||||
|
||||
def test_volume_create_with_source_volume(self):
|
||||
source_vol = "source_vol"
|
||||
source_volume = sdk_fakes.generate_fake_resource(_volume.Volume)
|
||||
self.volume_sdk_client.find_volume.return_value = source_volume
|
||||
|
||||
arglist = [
|
||||
'--source',
|
||||
self.new_volume.id,
|
||||
source_vol,
|
||||
source_volume.id,
|
||||
self.volume.name,
|
||||
]
|
||||
verifylist = [
|
||||
('source', self.new_volume.id),
|
||||
('name', source_vol),
|
||||
('source', source_volume.id),
|
||||
('name', self.volume.name),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.volumes_mock.get.return_value = self.new_volume
|
||||
|
||||
# In base command class ShowOne in cliff, abstract method take_action()
|
||||
# returns a two-part tuple with a tuple of column names and a tuple of
|
||||
# data to be shown.
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.volumes_mock.create.assert_called_once_with(
|
||||
size=self.new_volume.size,
|
||||
self.volume_sdk_client.create_volume.assert_called_with(
|
||||
size=source_volume.size,
|
||||
snapshot_id=None,
|
||||
name=source_vol,
|
||||
name=self.volume.name,
|
||||
description=None,
|
||||
volume_type=None,
|
||||
availability_zone=None,
|
||||
metadata=None,
|
||||
imageRef=None,
|
||||
source_volid=self.new_volume.id,
|
||||
consistencygroup_id=None,
|
||||
image_id=None,
|
||||
source_volume_id=source_volume.id,
|
||||
consistency_group_id=None,
|
||||
scheduler_hints=None,
|
||||
)
|
||||
self.volume_sdk_client.find_volume.assert_called_once_with(
|
||||
source_volume.id, ignore_missing=False
|
||||
)
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.datalist, data)
|
||||
self.assertEqual(self.datalist, data)
|
||||
|
||||
@mock.patch.object(utils, 'wait_for_status', return_value=True)
|
||||
def test_volume_create_with_bootable_and_readonly(self, mock_wait):
|
||||
@@ -374,42 +359,42 @@ class TestVolumeCreate(TestVolume):
|
||||
'--bootable',
|
||||
'--read-only',
|
||||
'--size',
|
||||
str(self.new_volume.size),
|
||||
self.new_volume.name,
|
||||
str(self.volume.size),
|
||||
self.volume.name,
|
||||
]
|
||||
verifylist = [
|
||||
('bootable', True),
|
||||
('read_only', True),
|
||||
('size', self.new_volume.size),
|
||||
('name', self.new_volume.name),
|
||||
('size', self.volume.size),
|
||||
('name', self.volume.name),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.volumes_mock.create.assert_called_with(
|
||||
size=self.new_volume.size,
|
||||
self.volume_sdk_client.create_volume.assert_called_with(
|
||||
size=self.volume.size,
|
||||
snapshot_id=None,
|
||||
name=self.new_volume.name,
|
||||
name=self.volume.name,
|
||||
description=None,
|
||||
volume_type=None,
|
||||
availability_zone=None,
|
||||
metadata=None,
|
||||
imageRef=None,
|
||||
source_volid=None,
|
||||
consistencygroup_id=None,
|
||||
image_id=None,
|
||||
source_volume_id=None,
|
||||
consistency_group_id=None,
|
||||
scheduler_hints=None,
|
||||
)
|
||||
self.volume_sdk_client.set_volume_bootable_status.assert_called_once_with(
|
||||
self.volume, True
|
||||
)
|
||||
self.volume_sdk_client.set_volume_readonly.assert_called_once_with(
|
||||
self.volume, True
|
||||
)
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.datalist, data)
|
||||
self.volumes_mock.set_bootable.assert_called_with(
|
||||
self.new_volume.id, True
|
||||
)
|
||||
self.volumes_mock.update_readonly_flag.assert_called_with(
|
||||
self.new_volume.id, True
|
||||
)
|
||||
self.assertEqual(self.datalist, data)
|
||||
|
||||
@mock.patch.object(utils, 'wait_for_status', return_value=True)
|
||||
def test_volume_create_with_nonbootable_and_readwrite(self, mock_wait):
|
||||
@@ -417,145 +402,144 @@ class TestVolumeCreate(TestVolume):
|
||||
'--non-bootable',
|
||||
'--read-write',
|
||||
'--size',
|
||||
str(self.new_volume.size),
|
||||
self.new_volume.name,
|
||||
str(self.volume.size),
|
||||
self.volume.name,
|
||||
]
|
||||
verifylist = [
|
||||
('bootable', False),
|
||||
('read_only', False),
|
||||
('size', self.new_volume.size),
|
||||
('name', self.new_volume.name),
|
||||
('size', self.volume.size),
|
||||
('name', self.volume.name),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.volumes_mock.create.assert_called_with(
|
||||
size=self.new_volume.size,
|
||||
self.volume_sdk_client.create_volume.assert_called_with(
|
||||
size=self.volume.size,
|
||||
snapshot_id=None,
|
||||
name=self.new_volume.name,
|
||||
name=self.volume.name,
|
||||
description=None,
|
||||
volume_type=None,
|
||||
availability_zone=None,
|
||||
metadata=None,
|
||||
imageRef=None,
|
||||
source_volid=None,
|
||||
consistencygroup_id=None,
|
||||
image_id=None,
|
||||
source_volume_id=None,
|
||||
consistency_group_id=None,
|
||||
scheduler_hints=None,
|
||||
)
|
||||
self.volume_sdk_client.set_volume_bootable_status.assert_called_once_with(
|
||||
self.volume, False
|
||||
)
|
||||
self.volume_sdk_client.set_volume_readonly.assert_called_once_with(
|
||||
self.volume, False
|
||||
)
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.datalist, data)
|
||||
self.volumes_mock.set_bootable.assert_called_with(
|
||||
self.new_volume.id, False
|
||||
)
|
||||
self.volumes_mock.update_readonly_flag.assert_called_with(
|
||||
self.new_volume.id, False
|
||||
)
|
||||
self.assertEqual(self.datalist, data)
|
||||
|
||||
@mock.patch.object(volume.LOG, 'error')
|
||||
@mock.patch.object(utils, 'wait_for_status', return_value=True)
|
||||
def test_volume_create_with_bootable_and_readonly_fail(
|
||||
self, mock_wait, mock_error
|
||||
):
|
||||
self.volumes_mock.set_bootable.side_effect = exceptions.CommandError()
|
||||
|
||||
self.volumes_mock.update_readonly_flag.side_effect = (
|
||||
exceptions.CommandError()
|
||||
self.volume_sdk_client.set_volume_bootable_status.side_effect = (
|
||||
sdk_exceptions.NotFoundException('foo')
|
||||
)
|
||||
self.volume_sdk_client.set_volume_readonly.side_effect = (
|
||||
sdk_exceptions.NotFoundException('foo')
|
||||
)
|
||||
|
||||
arglist = [
|
||||
'--bootable',
|
||||
'--read-only',
|
||||
'--size',
|
||||
str(self.new_volume.size),
|
||||
self.new_volume.name,
|
||||
str(self.volume.size),
|
||||
self.volume.name,
|
||||
]
|
||||
verifylist = [
|
||||
('bootable', True),
|
||||
('read_only', True),
|
||||
('size', self.new_volume.size),
|
||||
('name', self.new_volume.name),
|
||||
('size', self.volume.size),
|
||||
('name', self.volume.name),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.volumes_mock.create.assert_called_with(
|
||||
size=self.new_volume.size,
|
||||
self.volume_sdk_client.create_volume.assert_called_with(
|
||||
size=self.volume.size,
|
||||
snapshot_id=None,
|
||||
name=self.new_volume.name,
|
||||
name=self.volume.name,
|
||||
description=None,
|
||||
volume_type=None,
|
||||
availability_zone=None,
|
||||
metadata=None,
|
||||
imageRef=None,
|
||||
source_volid=None,
|
||||
consistencygroup_id=None,
|
||||
image_id=None,
|
||||
source_volume_id=None,
|
||||
consistency_group_id=None,
|
||||
scheduler_hints=None,
|
||||
)
|
||||
self.volume_sdk_client.set_volume_bootable_status.assert_called_once_with(
|
||||
self.volume, True
|
||||
)
|
||||
self.volume_sdk_client.set_volume_readonly.assert_called_once_with(
|
||||
self.volume, True
|
||||
)
|
||||
|
||||
self.assertEqual(2, mock_error.call_count)
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.datalist, data)
|
||||
self.volumes_mock.set_bootable.assert_called_with(
|
||||
self.new_volume.id, True
|
||||
)
|
||||
self.volumes_mock.update_readonly_flag.assert_called_with(
|
||||
self.new_volume.id, True
|
||||
)
|
||||
self.assertEqual(self.datalist, data)
|
||||
|
||||
@mock.patch.object(volume.LOG, 'error')
|
||||
@mock.patch.object(utils, 'wait_for_status', return_value=False)
|
||||
def test_volume_create_non_available_with_readonly(
|
||||
self,
|
||||
mock_wait,
|
||||
mock_error,
|
||||
self, mock_wait, mock_error
|
||||
):
|
||||
arglist = [
|
||||
'--non-bootable',
|
||||
'--read-only',
|
||||
'--size',
|
||||
str(self.new_volume.size),
|
||||
self.new_volume.name,
|
||||
str(self.volume.size),
|
||||
self.volume.name,
|
||||
]
|
||||
verifylist = [
|
||||
('bootable', False),
|
||||
('read_only', True),
|
||||
('size', self.new_volume.size),
|
||||
('name', self.new_volume.name),
|
||||
('size', self.volume.size),
|
||||
('name', self.volume.name),
|
||||
]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.volumes_mock.create.assert_called_with(
|
||||
size=self.new_volume.size,
|
||||
self.volume_sdk_client.create_volume.assert_called_with(
|
||||
size=self.volume.size,
|
||||
snapshot_id=None,
|
||||
name=self.new_volume.name,
|
||||
name=self.volume.name,
|
||||
description=None,
|
||||
volume_type=None,
|
||||
availability_zone=None,
|
||||
metadata=None,
|
||||
imageRef=None,
|
||||
source_volid=None,
|
||||
consistencygroup_id=None,
|
||||
image_id=None,
|
||||
source_volume_id=None,
|
||||
consistency_group_id=None,
|
||||
scheduler_hints=None,
|
||||
)
|
||||
|
||||
self.assertEqual(2, mock_error.call_count)
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.datalist, data)
|
||||
self.assertEqual(self.datalist, data)
|
||||
|
||||
def test_volume_create_without_size(self):
|
||||
arglist = [
|
||||
self.new_volume.name,
|
||||
self.volume.name,
|
||||
]
|
||||
verifylist = [
|
||||
('name', self.new_volume.name),
|
||||
('name', self.volume.name),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
@@ -572,15 +556,15 @@ class TestVolumeCreate(TestVolume):
|
||||
'--snapshot',
|
||||
'source_snapshot',
|
||||
'--size',
|
||||
str(self.new_volume.size),
|
||||
self.new_volume.name,
|
||||
str(self.volume.size),
|
||||
self.volume.name,
|
||||
]
|
||||
verifylist = [
|
||||
('image', 'source_image'),
|
||||
('source', 'source_volume'),
|
||||
('snapshot', 'source_snapshot'),
|
||||
('size', self.new_volume.size),
|
||||
('name', self.new_volume.name),
|
||||
('size', self.volume.size),
|
||||
('name', self.volume.name),
|
||||
]
|
||||
|
||||
self.assertRaises(
|
||||
@@ -599,7 +583,7 @@ class TestVolumeCreate(TestVolume):
|
||||
"""
|
||||
arglist = [
|
||||
'--size',
|
||||
str(self.new_volume.size),
|
||||
str(self.volume.size),
|
||||
'--hint',
|
||||
'k=v',
|
||||
'--hint',
|
||||
@@ -614,10 +598,10 @@ class TestVolumeCreate(TestVolume):
|
||||
'local_to_instance=v6',
|
||||
'--hint',
|
||||
'different_host=v7',
|
||||
self.new_volume.name,
|
||||
self.volume.name,
|
||||
]
|
||||
verifylist = [
|
||||
('size', self.new_volume.size),
|
||||
('size', self.volume.size),
|
||||
(
|
||||
'hint',
|
||||
{
|
||||
@@ -627,26 +611,23 @@ class TestVolumeCreate(TestVolume):
|
||||
'different_host': ['v5', 'v7'],
|
||||
},
|
||||
),
|
||||
('name', self.new_volume.name),
|
||||
('name', self.volume.name),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# In base command class ShowOne in cliff, abstract method take_action()
|
||||
# returns a two-part tuple with a tuple of column names and a tuple of
|
||||
# data to be shown.
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.volumes_mock.create.assert_called_with(
|
||||
size=self.new_volume.size,
|
||||
self.volume_sdk_client.create_volume.assert_called_with(
|
||||
size=self.volume.size,
|
||||
snapshot_id=None,
|
||||
name=self.new_volume.name,
|
||||
name=self.volume.name,
|
||||
description=None,
|
||||
volume_type=None,
|
||||
availability_zone=None,
|
||||
metadata=None,
|
||||
imageRef=None,
|
||||
source_volid=None,
|
||||
consistencygroup_id=None,
|
||||
image_id=None,
|
||||
source_volume_id=None,
|
||||
consistency_group_id=None,
|
||||
scheduler_hints={
|
||||
'k': 'v2',
|
||||
'same_host': ['v3', 'v4'],
|
||||
@@ -656,7 +637,7 @@ class TestVolumeCreate(TestVolume):
|
||||
)
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertCountEqual(self.datalist, data)
|
||||
self.assertEqual(self.datalist, data)
|
||||
|
||||
|
||||
class TestVolumeDelete(volume_fakes.TestVolume):
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -18,8 +18,10 @@ import argparse
|
||||
import copy
|
||||
import functools
|
||||
import logging
|
||||
import typing as ty
|
||||
|
||||
from cliff import columns as cliff_columns
|
||||
from openstack.block_storage.v2 import volume as _volume
|
||||
from openstack import exceptions as sdk_exceptions
|
||||
from osc_lib.cli import format_columns
|
||||
from osc_lib.cli import parseractions
|
||||
@@ -27,6 +29,7 @@ from osc_lib.command import command
|
||||
from osc_lib import exceptions
|
||||
from osc_lib import utils
|
||||
|
||||
from openstackclient.api import volume_v2
|
||||
from openstackclient.common import pagination
|
||||
from openstackclient.i18n import _
|
||||
from openstackclient.identity import common as identity_common
|
||||
@@ -89,6 +92,47 @@ class AttachmentsColumn(cliff_columns.FormattableColumn):
|
||||
return msg
|
||||
|
||||
|
||||
def _format_volume(volume: _volume.Volume) -> dict[str, ty.Any]:
|
||||
# Some columns returned by openstacksdk should not be shown because they're
|
||||
# either irrelevant or duplicates
|
||||
ignored_columns = {
|
||||
# computed columns
|
||||
'location',
|
||||
# create-only columns
|
||||
'OS-SCH-HNT:scheduler_hints',
|
||||
'imageRef',
|
||||
# unnecessary columns
|
||||
'links',
|
||||
}
|
||||
optional_columns = {
|
||||
# only present if part of a consistency group
|
||||
'consistencygroup_id',
|
||||
# only present if there are image properties associated
|
||||
'volume_image_metadata',
|
||||
}
|
||||
|
||||
info = volume.to_dict(original_names=True)
|
||||
data = {}
|
||||
for key, value in info.items():
|
||||
if key in ignored_columns:
|
||||
continue
|
||||
|
||||
if key in optional_columns:
|
||||
if info[key] is None:
|
||||
continue
|
||||
|
||||
data[key] = value
|
||||
|
||||
data.update(
|
||||
{
|
||||
'properties': format_columns.DictColumn(data.pop('metadata')),
|
||||
'type': data.pop('volume_type'),
|
||||
}
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class CreateVolume(command.ShowOne):
|
||||
_description = _("Create new volume")
|
||||
|
||||
@@ -226,22 +270,22 @@ class CreateVolume(command.ShowOne):
|
||||
# volume from snapshot or source volume
|
||||
size = parsed_args.size
|
||||
|
||||
volume_client = self.app.client_manager.volume
|
||||
volume_client = self.app.client_manager.sdk_connection.volume
|
||||
image_client = self.app.client_manager.image
|
||||
|
||||
source_volume = None
|
||||
if parsed_args.source:
|
||||
source_volume_obj = utils.find_resource(
|
||||
volume_client.volumes, parsed_args.source
|
||||
source_volume_obj = volume_client.find_volume(
|
||||
parsed_args.source, ignore_missing=False
|
||||
)
|
||||
source_volume = source_volume_obj.id
|
||||
size = max(size or 0, source_volume_obj.size)
|
||||
|
||||
consistency_group = None
|
||||
if parsed_args.consistency_group:
|
||||
consistency_group = utils.find_resource(
|
||||
volume_client.consistencygroups, parsed_args.consistency_group
|
||||
).id
|
||||
consistency_group = volume_v2.find_consistency_group(
|
||||
volume_client, parsed_args.consistency_group
|
||||
)['id']
|
||||
|
||||
image = None
|
||||
if parsed_args.image:
|
||||
@@ -251,8 +295,8 @@ class CreateVolume(command.ShowOne):
|
||||
|
||||
snapshot = None
|
||||
if parsed_args.snapshot:
|
||||
snapshot_obj = utils.find_resource(
|
||||
volume_client.volume_snapshots, parsed_args.snapshot
|
||||
snapshot_obj = volume_client.find_snapshot(
|
||||
parsed_args.snapshot, ignore_missing=False
|
||||
)
|
||||
snapshot = snapshot_obj.id
|
||||
# Cinder requires a value for size when creating a volume
|
||||
@@ -263,7 +307,7 @@ class CreateVolume(command.ShowOne):
|
||||
# snapshot size.
|
||||
size = max(size or 0, snapshot_obj.size)
|
||||
|
||||
volume = volume_client.volumes.create(
|
||||
volume = volume_client.create_volume(
|
||||
size=size,
|
||||
snapshot_id=snapshot,
|
||||
name=parsed_args.name,
|
||||
@@ -271,23 +315,23 @@ class CreateVolume(command.ShowOne):
|
||||
volume_type=parsed_args.type,
|
||||
availability_zone=parsed_args.availability_zone,
|
||||
metadata=parsed_args.properties,
|
||||
imageRef=image,
|
||||
source_volid=source_volume,
|
||||
consistencygroup_id=consistency_group,
|
||||
image_id=image,
|
||||
source_volume_id=source_volume,
|
||||
consistency_group_id=consistency_group,
|
||||
scheduler_hints=parsed_args.hint,
|
||||
)
|
||||
|
||||
if parsed_args.bootable is not None:
|
||||
try:
|
||||
if utils.wait_for_status(
|
||||
volume_client.volumes.get,
|
||||
volume_client.get_volume,
|
||||
volume.id,
|
||||
success_status=['available'],
|
||||
error_status=['error'],
|
||||
sleep_time=1,
|
||||
):
|
||||
volume_client.volumes.set_bootable(
|
||||
volume.id, parsed_args.bootable
|
||||
volume_client.set_volume_bootable_status(
|
||||
volume, parsed_args.bootable
|
||||
)
|
||||
else:
|
||||
msg = _(
|
||||
@@ -300,14 +344,14 @@ class CreateVolume(command.ShowOne):
|
||||
if parsed_args.read_only is not None:
|
||||
try:
|
||||
if utils.wait_for_status(
|
||||
volume_client.volumes.get,
|
||||
volume_client.get_volume,
|
||||
volume.id,
|
||||
success_status=['available'],
|
||||
error_status=['error'],
|
||||
sleep_time=1,
|
||||
):
|
||||
volume_client.volumes.update_readonly_flag(
|
||||
volume.id, parsed_args.read_only
|
||||
volume_client.set_volume_readonly(
|
||||
volume, parsed_args.read_only
|
||||
)
|
||||
else:
|
||||
msg = _(
|
||||
@@ -321,17 +365,8 @@ class CreateVolume(command.ShowOne):
|
||||
e,
|
||||
)
|
||||
|
||||
# Remove key links from being displayed
|
||||
volume._info.update(
|
||||
{
|
||||
'properties': format_columns.DictColumn(
|
||||
volume._info.pop('metadata')
|
||||
),
|
||||
'type': volume._info.pop('volume_type'),
|
||||
}
|
||||
)
|
||||
volume._info.pop("links", None)
|
||||
return zip(*sorted(volume._info.items()))
|
||||
data = _format_volume(volume)
|
||||
return zip(*sorted(data.items()))
|
||||
|
||||
|
||||
class DeleteVolume(command.Command):
|
||||
|
@@ -18,8 +18,10 @@ import argparse
|
||||
import copy
|
||||
import functools
|
||||
import logging
|
||||
import typing as ty
|
||||
|
||||
from cliff import columns as cliff_columns
|
||||
from openstack.block_storage.v3 import volume as _volume
|
||||
from openstack import exceptions as sdk_exceptions
|
||||
from openstack import utils as sdk_utils
|
||||
from osc_lib.cli import format_columns
|
||||
@@ -28,6 +30,7 @@ from osc_lib.command import command
|
||||
from osc_lib import exceptions
|
||||
from osc_lib import utils
|
||||
|
||||
from openstackclient.api import volume_v3
|
||||
from openstackclient.common import pagination
|
||||
from openstackclient.i18n import _
|
||||
from openstackclient.identity import common as identity_common
|
||||
@@ -90,6 +93,52 @@ class AttachmentsColumn(cliff_columns.FormattableColumn):
|
||||
return msg
|
||||
|
||||
|
||||
def _format_volume(volume: _volume.Volume) -> dict[str, ty.Any]:
|
||||
# Some columns returned by openstacksdk should not be shown because they're
|
||||
# either irrelevant or duplicates
|
||||
ignored_columns = {
|
||||
# computed columns
|
||||
'location',
|
||||
# create-only columns
|
||||
'OS-SCH-HNT:scheduler_hints',
|
||||
'imageRef',
|
||||
# removed columns
|
||||
'os-volume-replication:driver_data',
|
||||
'os-volume-replication:extended_status',
|
||||
# unnecessary columns
|
||||
'links',
|
||||
}
|
||||
optional_columns = {
|
||||
# only present if part of a consistency group
|
||||
'consistencygroup_id',
|
||||
# only present if the volume is encrypted
|
||||
'encryption_key_id',
|
||||
# only present if there are image properties associated
|
||||
'volume_image_metadata',
|
||||
}
|
||||
|
||||
info = volume.to_dict(original_names=True)
|
||||
data = {}
|
||||
for key, value in info.items():
|
||||
if key in ignored_columns:
|
||||
continue
|
||||
|
||||
if key in optional_columns:
|
||||
if info[key] is None:
|
||||
continue
|
||||
|
||||
data[key] = value
|
||||
|
||||
data.update(
|
||||
{
|
||||
'properties': format_columns.DictColumn(data.pop('metadata')),
|
||||
'type': data.pop('volume_type'),
|
||||
}
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class CreateVolume(command.ShowOne):
|
||||
_description = _("Create new volume")
|
||||
|
||||
@@ -272,8 +321,7 @@ class CreateVolume(command.ShowOne):
|
||||
# volume from snapshot, backup or source volume
|
||||
size = parsed_args.size
|
||||
|
||||
volume_client_sdk = self.app.client_manager.sdk_connection.volume
|
||||
volume_client = self.app.client_manager.volume
|
||||
volume_client = self.app.client_manager.sdk_connection.volume
|
||||
image_client = self.app.client_manager.image
|
||||
|
||||
if (
|
||||
@@ -285,8 +333,8 @@ class CreateVolume(command.ShowOne):
|
||||
)
|
||||
raise exceptions.CommandError(msg)
|
||||
|
||||
if parsed_args.backup and not (
|
||||
volume_client.api_version.matches('3.47')
|
||||
if parsed_args.backup and not sdk_utils.supports_microversion(
|
||||
volume_client, '3.47'
|
||||
):
|
||||
msg = _(
|
||||
"--os-volume-api-version 3.47 or greater is required "
|
||||
@@ -308,9 +356,7 @@ class CreateVolume(command.ShowOne):
|
||||
)
|
||||
raise exceptions.CommandError(msg)
|
||||
if parsed_args.cluster:
|
||||
if not sdk_utils.supports_microversion(
|
||||
volume_client_sdk, '3.16'
|
||||
):
|
||||
if not sdk_utils.supports_microversion(volume_client, '3.16'):
|
||||
msg = _(
|
||||
"--os-volume-api-version 3.16 or greater is required "
|
||||
"to support the cluster parameter."
|
||||
@@ -328,7 +374,7 @@ class CreateVolume(command.ShowOne):
|
||||
"manage a volume."
|
||||
)
|
||||
raise exceptions.CommandError(msg)
|
||||
volume = volume_client_sdk.manage_volume(
|
||||
volume = volume_client.manage_volume(
|
||||
host=parsed_args.host,
|
||||
cluster=parsed_args.cluster,
|
||||
ref=parsed_args.remote_source,
|
||||
@@ -339,35 +385,22 @@ class CreateVolume(command.ShowOne):
|
||||
metadata=parsed_args.properties,
|
||||
bootable=parsed_args.bootable,
|
||||
)
|
||||
data = {}
|
||||
for key, value in volume.to_dict().items():
|
||||
# FIXME(stephenfin): Stop ignoring these once we bump SDK
|
||||
# https://review.opendev.org/c/openstack/openstacksdk/+/945836/
|
||||
if key in (
|
||||
'cluster_name',
|
||||
'consumes_quota',
|
||||
'encryption_key_id',
|
||||
'service_uuid',
|
||||
'shared_targets',
|
||||
'volume_type_id',
|
||||
):
|
||||
continue
|
||||
data[key] = value
|
||||
data = _format_volume(volume)
|
||||
return zip(*sorted(data.items()))
|
||||
|
||||
source_volume = None
|
||||
if parsed_args.source:
|
||||
source_volume_obj = utils.find_resource(
|
||||
volume_client.volumes, parsed_args.source
|
||||
source_volume_obj = volume_client.find_volume(
|
||||
parsed_args.source, ignore_missing=False
|
||||
)
|
||||
source_volume = source_volume_obj.id
|
||||
size = max(size or 0, source_volume_obj.size)
|
||||
|
||||
consistency_group = None
|
||||
if parsed_args.consistency_group:
|
||||
consistency_group = utils.find_resource(
|
||||
volume_client.consistencygroups, parsed_args.consistency_group
|
||||
).id
|
||||
consistency_group = volume_v3.find_consistency_group(
|
||||
volume_client, parsed_args.consistency_group
|
||||
)['id']
|
||||
|
||||
image = None
|
||||
if parsed_args.image:
|
||||
@@ -377,8 +410,8 @@ class CreateVolume(command.ShowOne):
|
||||
|
||||
snapshot = None
|
||||
if parsed_args.snapshot:
|
||||
snapshot_obj = utils.find_resource(
|
||||
volume_client.volume_snapshots, parsed_args.snapshot
|
||||
snapshot_obj = volume_client.find_snapshot(
|
||||
parsed_args.snapshot, ignore_missing=False
|
||||
)
|
||||
snapshot = snapshot_obj.id
|
||||
# Cinder requires a value for size when creating a volume
|
||||
@@ -391,14 +424,14 @@ class CreateVolume(command.ShowOne):
|
||||
|
||||
backup = None
|
||||
if parsed_args.backup:
|
||||
backup_obj = utils.find_resource(
|
||||
volume_client.backups, parsed_args.backup
|
||||
backup_obj = volume_client.find_backup(
|
||||
parsed_args.backup, ignore_missing=False
|
||||
)
|
||||
backup = backup_obj.id
|
||||
# As above
|
||||
size = max(size or 0, backup_obj.size)
|
||||
|
||||
volume = volume_client.volumes.create(
|
||||
volume = volume_client.create_volume(
|
||||
size=size,
|
||||
snapshot_id=snapshot,
|
||||
name=parsed_args.name,
|
||||
@@ -406,9 +439,9 @@ class CreateVolume(command.ShowOne):
|
||||
volume_type=parsed_args.type,
|
||||
availability_zone=parsed_args.availability_zone,
|
||||
metadata=parsed_args.properties,
|
||||
imageRef=image,
|
||||
source_volid=source_volume,
|
||||
consistencygroup_id=consistency_group,
|
||||
image_id=image,
|
||||
source_volume_id=source_volume,
|
||||
consistency_group_id=consistency_group,
|
||||
scheduler_hints=parsed_args.hint,
|
||||
backup_id=backup,
|
||||
)
|
||||
@@ -416,14 +449,14 @@ class CreateVolume(command.ShowOne):
|
||||
if parsed_args.bootable is not None:
|
||||
try:
|
||||
if utils.wait_for_status(
|
||||
volume_client.volumes.get,
|
||||
volume_client.get_volume,
|
||||
volume.id,
|
||||
success_status=['available'],
|
||||
error_status=['error'],
|
||||
sleep_time=1,
|
||||
):
|
||||
volume_client.volumes.set_bootable(
|
||||
volume.id, parsed_args.bootable
|
||||
volume_client.set_volume_bootable_status(
|
||||
volume, parsed_args.bootable
|
||||
)
|
||||
else:
|
||||
msg = _(
|
||||
@@ -436,14 +469,14 @@ class CreateVolume(command.ShowOne):
|
||||
if parsed_args.read_only is not None:
|
||||
try:
|
||||
if utils.wait_for_status(
|
||||
volume_client.volumes.get,
|
||||
volume_client.get_volume,
|
||||
volume.id,
|
||||
success_status=['available'],
|
||||
error_status=['error'],
|
||||
sleep_time=1,
|
||||
):
|
||||
volume_client.volumes.update_readonly_flag(
|
||||
volume.id, parsed_args.read_only
|
||||
volume_client.set_volume_readonly(
|
||||
volume, parsed_args.read_only
|
||||
)
|
||||
else:
|
||||
msg = _(
|
||||
@@ -457,17 +490,8 @@ class CreateVolume(command.ShowOne):
|
||||
e,
|
||||
)
|
||||
|
||||
# Remove key links from being displayed
|
||||
volume._info.update(
|
||||
{
|
||||
'properties': format_columns.DictColumn(
|
||||
volume._info.pop('metadata')
|
||||
),
|
||||
'type': volume._info.pop('volume_type'),
|
||||
}
|
||||
)
|
||||
volume._info.pop("links", None)
|
||||
return zip(*sorted(volume._info.items()))
|
||||
data = _format_volume(volume)
|
||||
return zip(*sorted(data.items()))
|
||||
|
||||
|
||||
class DeleteVolume(command.Command):
|
||||
|
Reference in New Issue
Block a user