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):
|
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
|
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):
|
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
|
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"],
|
cmd_output["properties"],
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'false',
|
False,
|
||||||
cmd_output["bootable"],
|
cmd_output["bootable"],
|
||||||
)
|
)
|
||||||
self.wait_for_status("volume", name, "available")
|
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 unittest import mock
|
||||||
|
|
||||||
|
from openstack.block_storage.v2 import snapshot as _snapshot
|
||||||
from openstack.block_storage.v2 import volume as _volume
|
from openstack.block_storage.v2 import volume as _volume
|
||||||
from openstack import exceptions as sdk_exceptions
|
from openstack import exceptions as sdk_exceptions
|
||||||
from openstack.test import fakes as sdk_fakes
|
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 exceptions
|
||||||
from osc_lib import utils
|
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.identity.v3 import fakes as identity_fakes
|
||||||
from openstackclient.tests.unit.image.v2 import fakes as image_fakes
|
from openstackclient.tests.unit.image.v2 import fakes as image_fakes
|
||||||
from openstackclient.tests.unit import utils as test_utils
|
from openstackclient.tests.unit import utils as test_utils
|
||||||
@@ -50,129 +52,156 @@ class TestVolume(volume_fakes.TestVolume):
|
|||||||
self.consistencygroups_mock.reset_mock()
|
self.consistencygroups_mock.reset_mock()
|
||||||
|
|
||||||
|
|
||||||
class TestVolumeCreate(TestVolume):
|
class TestVolumeCreate(volume_fakes.TestVolume):
|
||||||
project = identity_fakes.FakeProject.create_one_project()
|
|
||||||
user = identity_fakes.FakeUser.create_one_user()
|
|
||||||
|
|
||||||
columns = (
|
columns = (
|
||||||
'attachments',
|
'attachments',
|
||||||
'availability_zone',
|
'availability_zone',
|
||||||
'bootable',
|
'bootable',
|
||||||
|
'consistencygroup_id',
|
||||||
|
'created_at',
|
||||||
'description',
|
'description',
|
||||||
|
'encrypted',
|
||||||
'id',
|
'id',
|
||||||
|
'multiattach',
|
||||||
'name',
|
'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',
|
'properties',
|
||||||
|
'replication_status',
|
||||||
'size',
|
'size',
|
||||||
'snapshot_id',
|
'snapshot_id',
|
||||||
|
'source_volid',
|
||||||
'status',
|
'status',
|
||||||
'type',
|
'type',
|
||||||
|
'updated_at',
|
||||||
|
'user_id',
|
||||||
|
'volume_image_metadata',
|
||||||
)
|
)
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
self.new_volume = volume_fakes.create_one_volume()
|
self.volume = sdk_fakes.generate_fake_resource(_volume.Volume)
|
||||||
self.volumes_mock.create.return_value = self.new_volume
|
self.volume_sdk_client.create_volume.return_value = self.volume
|
||||||
|
|
||||||
self.datalist = (
|
self.datalist = (
|
||||||
self.new_volume.attachments,
|
self.volume.attachments,
|
||||||
self.new_volume.availability_zone,
|
self.volume.availability_zone,
|
||||||
self.new_volume.bootable,
|
self.volume.is_bootable,
|
||||||
self.new_volume.description,
|
self.volume.consistency_group_id,
|
||||||
self.new_volume.id,
|
self.volume.created_at,
|
||||||
self.new_volume.name,
|
self.volume.description,
|
||||||
format_columns.DictColumn(self.new_volume.metadata),
|
self.volume.is_encrypted,
|
||||||
self.new_volume.size,
|
self.volume.id,
|
||||||
self.new_volume.snapshot_id,
|
self.volume.is_multiattach,
|
||||||
self.new_volume.status,
|
self.volume.name,
|
||||||
self.new_volume.volume_type,
|
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
|
# Get the command object to test
|
||||||
self.cmd = volume.CreateVolume(self.app, None)
|
self.cmd = volume.CreateVolume(self.app, None)
|
||||||
|
|
||||||
def test_volume_create_min_options(self):
|
def test_volume_create_min_options(self):
|
||||||
arglist = [
|
arglist = [
|
||||||
'--size',
|
'--size',
|
||||||
str(self.new_volume.size),
|
str(self.volume.size),
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('size', self.new_volume.size),
|
('size', self.volume.size),
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
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)
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self.volumes_mock.create.assert_called_with(
|
self.volume_sdk_client.create_volume.assert_called_with(
|
||||||
size=self.new_volume.size,
|
size=self.volume.size,
|
||||||
snapshot_id=None,
|
snapshot_id=None,
|
||||||
name=None,
|
name=None,
|
||||||
description=None,
|
description=None,
|
||||||
volume_type=None,
|
volume_type=None,
|
||||||
availability_zone=None,
|
availability_zone=None,
|
||||||
metadata=None,
|
metadata=None,
|
||||||
imageRef=None,
|
image_id=None,
|
||||||
source_volid=None,
|
source_volume_id=None,
|
||||||
consistencygroup_id=None,
|
consistency_group_id=None,
|
||||||
scheduler_hints=None,
|
scheduler_hints=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(self.columns, columns)
|
self.assertEqual(self.columns, columns)
|
||||||
self.assertCountEqual(self.datalist, data)
|
self.assertEqual(self.datalist, data)
|
||||||
|
|
||||||
def test_volume_create_options(self):
|
def test_volume_create_options(self):
|
||||||
consistency_group = volume_fakes.create_one_consistency_group()
|
consistency_group_id = 'cg123'
|
||||||
self.consistencygroups_mock.get.return_value = consistency_group
|
|
||||||
arglist = [
|
arglist = [
|
||||||
'--size',
|
'--size',
|
||||||
str(self.new_volume.size),
|
str(self.volume.size),
|
||||||
'--description',
|
'--description',
|
||||||
self.new_volume.description,
|
self.volume.description,
|
||||||
'--type',
|
'--type',
|
||||||
self.new_volume.volume_type,
|
self.volume.volume_type,
|
||||||
'--availability-zone',
|
'--availability-zone',
|
||||||
self.new_volume.availability_zone,
|
self.volume.availability_zone,
|
||||||
'--consistency-group',
|
'--consistency-group',
|
||||||
consistency_group.id,
|
consistency_group_id,
|
||||||
'--hint',
|
'--hint',
|
||||||
'k=v',
|
'k=v',
|
||||||
self.new_volume.name,
|
self.volume.name,
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('size', self.new_volume.size),
|
('size', self.volume.size),
|
||||||
('description', self.new_volume.description),
|
('description', self.volume.description),
|
||||||
('type', self.new_volume.volume_type),
|
('type', self.volume.volume_type),
|
||||||
('availability_zone', self.new_volume.availability_zone),
|
('availability_zone', self.volume.availability_zone),
|
||||||
('consistency_group', consistency_group.id),
|
('consistency_group', consistency_group_id),
|
||||||
('hint', {'k': 'v'}),
|
('hint', {'k': 'v'}),
|
||||||
('name', self.new_volume.name),
|
('name', self.volume.name),
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
# In base command class ShowOne in cliff, abstract method take_action()
|
with mock.patch.object(
|
||||||
# returns a two-part tuple with a tuple of column names and a tuple of
|
volume_v2,
|
||||||
# data to be shown.
|
'find_consistency_group',
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
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(
|
self.volume_sdk_client.create_volume.assert_called_with(
|
||||||
size=self.new_volume.size,
|
size=self.volume.size,
|
||||||
snapshot_id=None,
|
snapshot_id=None,
|
||||||
name=self.new_volume.name,
|
name=self.volume.name,
|
||||||
description=self.new_volume.description,
|
description=self.volume.description,
|
||||||
volume_type=self.new_volume.volume_type,
|
volume_type=self.volume.volume_type,
|
||||||
availability_zone=self.new_volume.availability_zone,
|
availability_zone=self.volume.availability_zone,
|
||||||
metadata=None,
|
metadata=None,
|
||||||
imageRef=None,
|
image_id=None,
|
||||||
source_volid=None,
|
source_volume_id=None,
|
||||||
consistencygroup_id=consistency_group.id,
|
consistency_group_id=consistency_group_id,
|
||||||
scheduler_hints={'k': 'v'},
|
scheduler_hints={'k': 'v'},
|
||||||
)
|
)
|
||||||
|
mock_find_cg.assert_called_once_with(
|
||||||
|
self.volume_sdk_client, consistency_group_id
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(self.columns, columns)
|
self.assertEqual(self.columns, columns)
|
||||||
self.assertCountEqual(self.datalist, data)
|
self.assertEqual(self.datalist, data)
|
||||||
|
|
||||||
def test_volume_create_properties(self):
|
def test_volume_create_properties(self):
|
||||||
arglist = [
|
arglist = [
|
||||||
@@ -181,39 +210,36 @@ class TestVolumeCreate(TestVolume):
|
|||||||
'--property',
|
'--property',
|
||||||
'Beta=b',
|
'Beta=b',
|
||||||
'--size',
|
'--size',
|
||||||
str(self.new_volume.size),
|
str(self.volume.size),
|
||||||
self.new_volume.name,
|
self.volume.name,
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('properties', {'Alpha': 'a', 'Beta': 'b'}),
|
('properties', {'Alpha': 'a', 'Beta': 'b'}),
|
||||||
('size', self.new_volume.size),
|
('size', self.volume.size),
|
||||||
('name', self.new_volume.name),
|
('name', self.volume.name),
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
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)
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self.volumes_mock.create.assert_called_with(
|
self.volume_sdk_client.create_volume.assert_called_with(
|
||||||
size=self.new_volume.size,
|
size=self.volume.size,
|
||||||
snapshot_id=None,
|
snapshot_id=None,
|
||||||
name=self.new_volume.name,
|
name=self.volume.name,
|
||||||
description=None,
|
description=None,
|
||||||
volume_type=None,
|
volume_type=None,
|
||||||
availability_zone=None,
|
availability_zone=None,
|
||||||
metadata={'Alpha': 'a', 'Beta': 'b'},
|
metadata={'Alpha': 'a', 'Beta': 'b'},
|
||||||
imageRef=None,
|
image_id=None,
|
||||||
source_volid=None,
|
source_volume_id=None,
|
||||||
consistencygroup_id=None,
|
consistency_group_id=None,
|
||||||
scheduler_hints=None,
|
scheduler_hints=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(self.columns, columns)
|
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()
|
image = image_fakes.create_one_image()
|
||||||
self.image_client.find_image.return_value = image
|
self.image_client.find_image.return_value = image
|
||||||
|
|
||||||
@@ -221,152 +247,111 @@ class TestVolumeCreate(TestVolume):
|
|||||||
'--image',
|
'--image',
|
||||||
image.id,
|
image.id,
|
||||||
'--size',
|
'--size',
|
||||||
str(self.new_volume.size),
|
str(self.volume.size),
|
||||||
self.new_volume.name,
|
self.volume.name,
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('image', image.id),
|
('image', image.id),
|
||||||
('size', self.new_volume.size),
|
('size', self.volume.size),
|
||||||
('name', self.new_volume.name),
|
('name', self.volume.name),
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
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)
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self.volumes_mock.create.assert_called_with(
|
self.volume_sdk_client.create_volume.assert_called_with(
|
||||||
size=self.new_volume.size,
|
size=self.volume.size,
|
||||||
snapshot_id=None,
|
snapshot_id=None,
|
||||||
name=self.new_volume.name,
|
name=self.volume.name,
|
||||||
description=None,
|
description=None,
|
||||||
volume_type=None,
|
volume_type=None,
|
||||||
availability_zone=None,
|
availability_zone=None,
|
||||||
metadata=None,
|
metadata=None,
|
||||||
imageRef=image.id,
|
image_id=image.id,
|
||||||
source_volid=None,
|
source_volume_id=None,
|
||||||
consistencygroup_id=None,
|
consistency_group_id=None,
|
||||||
scheduler_hints=None,
|
scheduler_hints=None,
|
||||||
)
|
)
|
||||||
|
self.image_client.find_image.assert_called_once_with(
|
||||||
|
image.id, ignore_missing=False
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(self.columns, columns)
|
self.assertEqual(self.columns, columns)
|
||||||
self.assertCountEqual(self.datalist, data)
|
self.assertEqual(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)
|
|
||||||
|
|
||||||
def test_volume_create_with_snapshot(self):
|
def test_volume_create_with_snapshot(self):
|
||||||
snapshot = volume_fakes.create_one_snapshot()
|
snapshot = sdk_fakes.generate_fake_resource(_snapshot.Snapshot)
|
||||||
self.new_volume.snapshot_id = snapshot.id
|
self.volume_sdk_client.find_snapshot.return_value = snapshot
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
'--snapshot',
|
'--snapshot',
|
||||||
self.new_volume.snapshot_id,
|
snapshot.id,
|
||||||
self.new_volume.name,
|
self.volume.name,
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('snapshot', self.new_volume.snapshot_id),
|
('snapshot', snapshot.id),
|
||||||
('name', self.new_volume.name),
|
('name', self.volume.name),
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
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)
|
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,
|
size=snapshot.size,
|
||||||
snapshot_id=snapshot.id,
|
snapshot_id=snapshot.id,
|
||||||
name=self.new_volume.name,
|
name=self.volume.name,
|
||||||
description=None,
|
description=None,
|
||||||
volume_type=None,
|
volume_type=None,
|
||||||
availability_zone=None,
|
availability_zone=None,
|
||||||
metadata=None,
|
metadata=None,
|
||||||
imageRef=None,
|
image_id=None,
|
||||||
source_volid=None,
|
source_volume_id=None,
|
||||||
consistencygroup_id=None,
|
consistency_group_id=None,
|
||||||
scheduler_hints=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.assertEqual(self.columns, columns)
|
||||||
self.assertCountEqual(self.datalist, data)
|
self.assertEqual(self.datalist, data)
|
||||||
|
|
||||||
def test_volume_create_with_source_volume(self):
|
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 = [
|
arglist = [
|
||||||
'--source',
|
'--source',
|
||||||
self.new_volume.id,
|
source_volume.id,
|
||||||
source_vol,
|
self.volume.name,
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('source', self.new_volume.id),
|
('source', source_volume.id),
|
||||||
('name', source_vol),
|
('name', self.volume.name),
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
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)
|
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=self.new_volume.size,
|
size=source_volume.size,
|
||||||
snapshot_id=None,
|
snapshot_id=None,
|
||||||
name=source_vol,
|
name=self.volume.name,
|
||||||
description=None,
|
description=None,
|
||||||
volume_type=None,
|
volume_type=None,
|
||||||
availability_zone=None,
|
availability_zone=None,
|
||||||
metadata=None,
|
metadata=None,
|
||||||
imageRef=None,
|
image_id=None,
|
||||||
source_volid=self.new_volume.id,
|
source_volume_id=source_volume.id,
|
||||||
consistencygroup_id=None,
|
consistency_group_id=None,
|
||||||
scheduler_hints=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.assertEqual(self.columns, columns)
|
||||||
self.assertCountEqual(self.datalist, data)
|
self.assertEqual(self.datalist, data)
|
||||||
|
|
||||||
@mock.patch.object(utils, 'wait_for_status', return_value=True)
|
@mock.patch.object(utils, 'wait_for_status', return_value=True)
|
||||||
def test_volume_create_with_bootable_and_readonly(self, mock_wait):
|
def test_volume_create_with_bootable_and_readonly(self, mock_wait):
|
||||||
@@ -374,42 +359,42 @@ class TestVolumeCreate(TestVolume):
|
|||||||
'--bootable',
|
'--bootable',
|
||||||
'--read-only',
|
'--read-only',
|
||||||
'--size',
|
'--size',
|
||||||
str(self.new_volume.size),
|
str(self.volume.size),
|
||||||
self.new_volume.name,
|
self.volume.name,
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('bootable', True),
|
('bootable', True),
|
||||||
('read_only', True),
|
('read_only', True),
|
||||||
('size', self.new_volume.size),
|
('size', self.volume.size),
|
||||||
('name', self.new_volume.name),
|
('name', self.volume.name),
|
||||||
]
|
]
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self.volumes_mock.create.assert_called_with(
|
self.volume_sdk_client.create_volume.assert_called_with(
|
||||||
size=self.new_volume.size,
|
size=self.volume.size,
|
||||||
snapshot_id=None,
|
snapshot_id=None,
|
||||||
name=self.new_volume.name,
|
name=self.volume.name,
|
||||||
description=None,
|
description=None,
|
||||||
volume_type=None,
|
volume_type=None,
|
||||||
availability_zone=None,
|
availability_zone=None,
|
||||||
metadata=None,
|
metadata=None,
|
||||||
imageRef=None,
|
image_id=None,
|
||||||
source_volid=None,
|
source_volume_id=None,
|
||||||
consistencygroup_id=None,
|
consistency_group_id=None,
|
||||||
scheduler_hints=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.assertEqual(self.columns, columns)
|
||||||
self.assertCountEqual(self.datalist, data)
|
self.assertEqual(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
|
|
||||||
)
|
|
||||||
|
|
||||||
@mock.patch.object(utils, 'wait_for_status', return_value=True)
|
@mock.patch.object(utils, 'wait_for_status', return_value=True)
|
||||||
def test_volume_create_with_nonbootable_and_readwrite(self, mock_wait):
|
def test_volume_create_with_nonbootable_and_readwrite(self, mock_wait):
|
||||||
@@ -417,145 +402,144 @@ class TestVolumeCreate(TestVolume):
|
|||||||
'--non-bootable',
|
'--non-bootable',
|
||||||
'--read-write',
|
'--read-write',
|
||||||
'--size',
|
'--size',
|
||||||
str(self.new_volume.size),
|
str(self.volume.size),
|
||||||
self.new_volume.name,
|
self.volume.name,
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('bootable', False),
|
('bootable', False),
|
||||||
('read_only', False),
|
('read_only', False),
|
||||||
('size', self.new_volume.size),
|
('size', self.volume.size),
|
||||||
('name', self.new_volume.name),
|
('name', self.volume.name),
|
||||||
]
|
]
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self.volumes_mock.create.assert_called_with(
|
self.volume_sdk_client.create_volume.assert_called_with(
|
||||||
size=self.new_volume.size,
|
size=self.volume.size,
|
||||||
snapshot_id=None,
|
snapshot_id=None,
|
||||||
name=self.new_volume.name,
|
name=self.volume.name,
|
||||||
description=None,
|
description=None,
|
||||||
volume_type=None,
|
volume_type=None,
|
||||||
availability_zone=None,
|
availability_zone=None,
|
||||||
metadata=None,
|
metadata=None,
|
||||||
imageRef=None,
|
image_id=None,
|
||||||
source_volid=None,
|
source_volume_id=None,
|
||||||
consistencygroup_id=None,
|
consistency_group_id=None,
|
||||||
scheduler_hints=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.assertEqual(self.columns, columns)
|
||||||
self.assertCountEqual(self.datalist, data)
|
self.assertEqual(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
|
|
||||||
)
|
|
||||||
|
|
||||||
@mock.patch.object(volume.LOG, 'error')
|
@mock.patch.object(volume.LOG, 'error')
|
||||||
@mock.patch.object(utils, 'wait_for_status', return_value=True)
|
@mock.patch.object(utils, 'wait_for_status', return_value=True)
|
||||||
def test_volume_create_with_bootable_and_readonly_fail(
|
def test_volume_create_with_bootable_and_readonly_fail(
|
||||||
self, mock_wait, mock_error
|
self, mock_wait, mock_error
|
||||||
):
|
):
|
||||||
self.volumes_mock.set_bootable.side_effect = exceptions.CommandError()
|
self.volume_sdk_client.set_volume_bootable_status.side_effect = (
|
||||||
|
sdk_exceptions.NotFoundException('foo')
|
||||||
self.volumes_mock.update_readonly_flag.side_effect = (
|
)
|
||||||
exceptions.CommandError()
|
self.volume_sdk_client.set_volume_readonly.side_effect = (
|
||||||
|
sdk_exceptions.NotFoundException('foo')
|
||||||
)
|
)
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
'--bootable',
|
'--bootable',
|
||||||
'--read-only',
|
'--read-only',
|
||||||
'--size',
|
'--size',
|
||||||
str(self.new_volume.size),
|
str(self.volume.size),
|
||||||
self.new_volume.name,
|
self.volume.name,
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('bootable', True),
|
('bootable', True),
|
||||||
('read_only', True),
|
('read_only', True),
|
||||||
('size', self.new_volume.size),
|
('size', self.volume.size),
|
||||||
('name', self.new_volume.name),
|
('name', self.volume.name),
|
||||||
]
|
]
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self.volumes_mock.create.assert_called_with(
|
self.volume_sdk_client.create_volume.assert_called_with(
|
||||||
size=self.new_volume.size,
|
size=self.volume.size,
|
||||||
snapshot_id=None,
|
snapshot_id=None,
|
||||||
name=self.new_volume.name,
|
name=self.volume.name,
|
||||||
description=None,
|
description=None,
|
||||||
volume_type=None,
|
volume_type=None,
|
||||||
availability_zone=None,
|
availability_zone=None,
|
||||||
metadata=None,
|
metadata=None,
|
||||||
imageRef=None,
|
image_id=None,
|
||||||
source_volid=None,
|
source_volume_id=None,
|
||||||
consistencygroup_id=None,
|
consistency_group_id=None,
|
||||||
scheduler_hints=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(2, mock_error.call_count)
|
||||||
self.assertEqual(self.columns, columns)
|
self.assertEqual(self.columns, columns)
|
||||||
self.assertCountEqual(self.datalist, data)
|
self.assertEqual(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
|
|
||||||
)
|
|
||||||
|
|
||||||
@mock.patch.object(volume.LOG, 'error')
|
@mock.patch.object(volume.LOG, 'error')
|
||||||
@mock.patch.object(utils, 'wait_for_status', return_value=False)
|
@mock.patch.object(utils, 'wait_for_status', return_value=False)
|
||||||
def test_volume_create_non_available_with_readonly(
|
def test_volume_create_non_available_with_readonly(
|
||||||
self,
|
self, mock_wait, mock_error
|
||||||
mock_wait,
|
|
||||||
mock_error,
|
|
||||||
):
|
):
|
||||||
arglist = [
|
arglist = [
|
||||||
'--non-bootable',
|
'--non-bootable',
|
||||||
'--read-only',
|
'--read-only',
|
||||||
'--size',
|
'--size',
|
||||||
str(self.new_volume.size),
|
str(self.volume.size),
|
||||||
self.new_volume.name,
|
self.volume.name,
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('bootable', False),
|
('bootable', False),
|
||||||
('read_only', True),
|
('read_only', True),
|
||||||
('size', self.new_volume.size),
|
('size', self.volume.size),
|
||||||
('name', self.new_volume.name),
|
('name', self.volume.name),
|
||||||
]
|
]
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self.volumes_mock.create.assert_called_with(
|
self.volume_sdk_client.create_volume.assert_called_with(
|
||||||
size=self.new_volume.size,
|
size=self.volume.size,
|
||||||
snapshot_id=None,
|
snapshot_id=None,
|
||||||
name=self.new_volume.name,
|
name=self.volume.name,
|
||||||
description=None,
|
description=None,
|
||||||
volume_type=None,
|
volume_type=None,
|
||||||
availability_zone=None,
|
availability_zone=None,
|
||||||
metadata=None,
|
metadata=None,
|
||||||
imageRef=None,
|
image_id=None,
|
||||||
source_volid=None,
|
source_volume_id=None,
|
||||||
consistencygroup_id=None,
|
consistency_group_id=None,
|
||||||
scheduler_hints=None,
|
scheduler_hints=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(2, mock_error.call_count)
|
self.assertEqual(2, mock_error.call_count)
|
||||||
self.assertEqual(self.columns, columns)
|
self.assertEqual(self.columns, columns)
|
||||||
self.assertCountEqual(self.datalist, data)
|
self.assertEqual(self.datalist, data)
|
||||||
|
|
||||||
def test_volume_create_without_size(self):
|
def test_volume_create_without_size(self):
|
||||||
arglist = [
|
arglist = [
|
||||||
self.new_volume.name,
|
self.volume.name,
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('name', self.new_volume.name),
|
('name', self.volume.name),
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
@@ -572,15 +556,15 @@ class TestVolumeCreate(TestVolume):
|
|||||||
'--snapshot',
|
'--snapshot',
|
||||||
'source_snapshot',
|
'source_snapshot',
|
||||||
'--size',
|
'--size',
|
||||||
str(self.new_volume.size),
|
str(self.volume.size),
|
||||||
self.new_volume.name,
|
self.volume.name,
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('image', 'source_image'),
|
('image', 'source_image'),
|
||||||
('source', 'source_volume'),
|
('source', 'source_volume'),
|
||||||
('snapshot', 'source_snapshot'),
|
('snapshot', 'source_snapshot'),
|
||||||
('size', self.new_volume.size),
|
('size', self.volume.size),
|
||||||
('name', self.new_volume.name),
|
('name', self.volume.name),
|
||||||
]
|
]
|
||||||
|
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
@@ -599,7 +583,7 @@ class TestVolumeCreate(TestVolume):
|
|||||||
"""
|
"""
|
||||||
arglist = [
|
arglist = [
|
||||||
'--size',
|
'--size',
|
||||||
str(self.new_volume.size),
|
str(self.volume.size),
|
||||||
'--hint',
|
'--hint',
|
||||||
'k=v',
|
'k=v',
|
||||||
'--hint',
|
'--hint',
|
||||||
@@ -614,10 +598,10 @@ class TestVolumeCreate(TestVolume):
|
|||||||
'local_to_instance=v6',
|
'local_to_instance=v6',
|
||||||
'--hint',
|
'--hint',
|
||||||
'different_host=v7',
|
'different_host=v7',
|
||||||
self.new_volume.name,
|
self.volume.name,
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('size', self.new_volume.size),
|
('size', self.volume.size),
|
||||||
(
|
(
|
||||||
'hint',
|
'hint',
|
||||||
{
|
{
|
||||||
@@ -627,26 +611,23 @@ class TestVolumeCreate(TestVolume):
|
|||||||
'different_host': ['v5', 'v7'],
|
'different_host': ['v5', 'v7'],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
('name', self.new_volume.name),
|
('name', self.volume.name),
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
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)
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self.volumes_mock.create.assert_called_with(
|
self.volume_sdk_client.create_volume.assert_called_with(
|
||||||
size=self.new_volume.size,
|
size=self.volume.size,
|
||||||
snapshot_id=None,
|
snapshot_id=None,
|
||||||
name=self.new_volume.name,
|
name=self.volume.name,
|
||||||
description=None,
|
description=None,
|
||||||
volume_type=None,
|
volume_type=None,
|
||||||
availability_zone=None,
|
availability_zone=None,
|
||||||
metadata=None,
|
metadata=None,
|
||||||
imageRef=None,
|
image_id=None,
|
||||||
source_volid=None,
|
source_volume_id=None,
|
||||||
consistencygroup_id=None,
|
consistency_group_id=None,
|
||||||
scheduler_hints={
|
scheduler_hints={
|
||||||
'k': 'v2',
|
'k': 'v2',
|
||||||
'same_host': ['v3', 'v4'],
|
'same_host': ['v3', 'v4'],
|
||||||
@@ -656,7 +637,7 @@ class TestVolumeCreate(TestVolume):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(self.columns, columns)
|
self.assertEqual(self.columns, columns)
|
||||||
self.assertCountEqual(self.datalist, data)
|
self.assertEqual(self.datalist, data)
|
||||||
|
|
||||||
|
|
||||||
class TestVolumeDelete(volume_fakes.TestVolume):
|
class TestVolumeDelete(volume_fakes.TestVolume):
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -18,8 +18,10 @@ import argparse
|
|||||||
import copy
|
import copy
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
|
import typing as ty
|
||||||
|
|
||||||
from cliff import columns as cliff_columns
|
from cliff import columns as cliff_columns
|
||||||
|
from openstack.block_storage.v2 import volume as _volume
|
||||||
from openstack import exceptions as sdk_exceptions
|
from openstack import exceptions as sdk_exceptions
|
||||||
from osc_lib.cli import format_columns
|
from osc_lib.cli import format_columns
|
||||||
from osc_lib.cli import parseractions
|
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 exceptions
|
||||||
from osc_lib import utils
|
from osc_lib import utils
|
||||||
|
|
||||||
|
from openstackclient.api import volume_v2
|
||||||
from openstackclient.common import pagination
|
from openstackclient.common import pagination
|
||||||
from openstackclient.i18n import _
|
from openstackclient.i18n import _
|
||||||
from openstackclient.identity import common as identity_common
|
from openstackclient.identity import common as identity_common
|
||||||
@@ -89,6 +92,47 @@ class AttachmentsColumn(cliff_columns.FormattableColumn):
|
|||||||
return msg
|
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):
|
class CreateVolume(command.ShowOne):
|
||||||
_description = _("Create new volume")
|
_description = _("Create new volume")
|
||||||
|
|
||||||
@@ -226,22 +270,22 @@ class CreateVolume(command.ShowOne):
|
|||||||
# volume from snapshot or source volume
|
# volume from snapshot or source volume
|
||||||
size = parsed_args.size
|
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
|
image_client = self.app.client_manager.image
|
||||||
|
|
||||||
source_volume = None
|
source_volume = None
|
||||||
if parsed_args.source:
|
if parsed_args.source:
|
||||||
source_volume_obj = utils.find_resource(
|
source_volume_obj = volume_client.find_volume(
|
||||||
volume_client.volumes, parsed_args.source
|
parsed_args.source, ignore_missing=False
|
||||||
)
|
)
|
||||||
source_volume = source_volume_obj.id
|
source_volume = source_volume_obj.id
|
||||||
size = max(size or 0, source_volume_obj.size)
|
size = max(size or 0, source_volume_obj.size)
|
||||||
|
|
||||||
consistency_group = None
|
consistency_group = None
|
||||||
if parsed_args.consistency_group:
|
if parsed_args.consistency_group:
|
||||||
consistency_group = utils.find_resource(
|
consistency_group = volume_v2.find_consistency_group(
|
||||||
volume_client.consistencygroups, parsed_args.consistency_group
|
volume_client, parsed_args.consistency_group
|
||||||
).id
|
)['id']
|
||||||
|
|
||||||
image = None
|
image = None
|
||||||
if parsed_args.image:
|
if parsed_args.image:
|
||||||
@@ -251,8 +295,8 @@ class CreateVolume(command.ShowOne):
|
|||||||
|
|
||||||
snapshot = None
|
snapshot = None
|
||||||
if parsed_args.snapshot:
|
if parsed_args.snapshot:
|
||||||
snapshot_obj = utils.find_resource(
|
snapshot_obj = volume_client.find_snapshot(
|
||||||
volume_client.volume_snapshots, parsed_args.snapshot
|
parsed_args.snapshot, ignore_missing=False
|
||||||
)
|
)
|
||||||
snapshot = snapshot_obj.id
|
snapshot = snapshot_obj.id
|
||||||
# Cinder requires a value for size when creating a volume
|
# Cinder requires a value for size when creating a volume
|
||||||
@@ -263,7 +307,7 @@ class CreateVolume(command.ShowOne):
|
|||||||
# snapshot size.
|
# snapshot size.
|
||||||
size = max(size or 0, snapshot_obj.size)
|
size = max(size or 0, snapshot_obj.size)
|
||||||
|
|
||||||
volume = volume_client.volumes.create(
|
volume = volume_client.create_volume(
|
||||||
size=size,
|
size=size,
|
||||||
snapshot_id=snapshot,
|
snapshot_id=snapshot,
|
||||||
name=parsed_args.name,
|
name=parsed_args.name,
|
||||||
@@ -271,23 +315,23 @@ class CreateVolume(command.ShowOne):
|
|||||||
volume_type=parsed_args.type,
|
volume_type=parsed_args.type,
|
||||||
availability_zone=parsed_args.availability_zone,
|
availability_zone=parsed_args.availability_zone,
|
||||||
metadata=parsed_args.properties,
|
metadata=parsed_args.properties,
|
||||||
imageRef=image,
|
image_id=image,
|
||||||
source_volid=source_volume,
|
source_volume_id=source_volume,
|
||||||
consistencygroup_id=consistency_group,
|
consistency_group_id=consistency_group,
|
||||||
scheduler_hints=parsed_args.hint,
|
scheduler_hints=parsed_args.hint,
|
||||||
)
|
)
|
||||||
|
|
||||||
if parsed_args.bootable is not None:
|
if parsed_args.bootable is not None:
|
||||||
try:
|
try:
|
||||||
if utils.wait_for_status(
|
if utils.wait_for_status(
|
||||||
volume_client.volumes.get,
|
volume_client.get_volume,
|
||||||
volume.id,
|
volume.id,
|
||||||
success_status=['available'],
|
success_status=['available'],
|
||||||
error_status=['error'],
|
error_status=['error'],
|
||||||
sleep_time=1,
|
sleep_time=1,
|
||||||
):
|
):
|
||||||
volume_client.volumes.set_bootable(
|
volume_client.set_volume_bootable_status(
|
||||||
volume.id, parsed_args.bootable
|
volume, parsed_args.bootable
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
msg = _(
|
msg = _(
|
||||||
@@ -300,14 +344,14 @@ class CreateVolume(command.ShowOne):
|
|||||||
if parsed_args.read_only is not None:
|
if parsed_args.read_only is not None:
|
||||||
try:
|
try:
|
||||||
if utils.wait_for_status(
|
if utils.wait_for_status(
|
||||||
volume_client.volumes.get,
|
volume_client.get_volume,
|
||||||
volume.id,
|
volume.id,
|
||||||
success_status=['available'],
|
success_status=['available'],
|
||||||
error_status=['error'],
|
error_status=['error'],
|
||||||
sleep_time=1,
|
sleep_time=1,
|
||||||
):
|
):
|
||||||
volume_client.volumes.update_readonly_flag(
|
volume_client.set_volume_readonly(
|
||||||
volume.id, parsed_args.read_only
|
volume, parsed_args.read_only
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
msg = _(
|
msg = _(
|
||||||
@@ -321,17 +365,8 @@ class CreateVolume(command.ShowOne):
|
|||||||
e,
|
e,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Remove key links from being displayed
|
data = _format_volume(volume)
|
||||||
volume._info.update(
|
return zip(*sorted(data.items()))
|
||||||
{
|
|
||||||
'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()))
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteVolume(command.Command):
|
class DeleteVolume(command.Command):
|
||||||
|
@@ -18,8 +18,10 @@ import argparse
|
|||||||
import copy
|
import copy
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
|
import typing as ty
|
||||||
|
|
||||||
from cliff import columns as cliff_columns
|
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 exceptions as sdk_exceptions
|
||||||
from openstack import utils as sdk_utils
|
from openstack import utils as sdk_utils
|
||||||
from osc_lib.cli import format_columns
|
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 exceptions
|
||||||
from osc_lib import utils
|
from osc_lib import utils
|
||||||
|
|
||||||
|
from openstackclient.api import volume_v3
|
||||||
from openstackclient.common import pagination
|
from openstackclient.common import pagination
|
||||||
from openstackclient.i18n import _
|
from openstackclient.i18n import _
|
||||||
from openstackclient.identity import common as identity_common
|
from openstackclient.identity import common as identity_common
|
||||||
@@ -90,6 +93,52 @@ class AttachmentsColumn(cliff_columns.FormattableColumn):
|
|||||||
return msg
|
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):
|
class CreateVolume(command.ShowOne):
|
||||||
_description = _("Create new volume")
|
_description = _("Create new volume")
|
||||||
|
|
||||||
@@ -272,8 +321,7 @@ class CreateVolume(command.ShowOne):
|
|||||||
# volume from snapshot, backup or source volume
|
# volume from snapshot, backup or source volume
|
||||||
size = parsed_args.size
|
size = parsed_args.size
|
||||||
|
|
||||||
volume_client_sdk = self.app.client_manager.sdk_connection.volume
|
volume_client = self.app.client_manager.sdk_connection.volume
|
||||||
volume_client = self.app.client_manager.volume
|
|
||||||
image_client = self.app.client_manager.image
|
image_client = self.app.client_manager.image
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -285,8 +333,8 @@ class CreateVolume(command.ShowOne):
|
|||||||
)
|
)
|
||||||
raise exceptions.CommandError(msg)
|
raise exceptions.CommandError(msg)
|
||||||
|
|
||||||
if parsed_args.backup and not (
|
if parsed_args.backup and not sdk_utils.supports_microversion(
|
||||||
volume_client.api_version.matches('3.47')
|
volume_client, '3.47'
|
||||||
):
|
):
|
||||||
msg = _(
|
msg = _(
|
||||||
"--os-volume-api-version 3.47 or greater is required "
|
"--os-volume-api-version 3.47 or greater is required "
|
||||||
@@ -308,9 +356,7 @@ class CreateVolume(command.ShowOne):
|
|||||||
)
|
)
|
||||||
raise exceptions.CommandError(msg)
|
raise exceptions.CommandError(msg)
|
||||||
if parsed_args.cluster:
|
if parsed_args.cluster:
|
||||||
if not sdk_utils.supports_microversion(
|
if not sdk_utils.supports_microversion(volume_client, '3.16'):
|
||||||
volume_client_sdk, '3.16'
|
|
||||||
):
|
|
||||||
msg = _(
|
msg = _(
|
||||||
"--os-volume-api-version 3.16 or greater is required "
|
"--os-volume-api-version 3.16 or greater is required "
|
||||||
"to support the cluster parameter."
|
"to support the cluster parameter."
|
||||||
@@ -328,7 +374,7 @@ class CreateVolume(command.ShowOne):
|
|||||||
"manage a volume."
|
"manage a volume."
|
||||||
)
|
)
|
||||||
raise exceptions.CommandError(msg)
|
raise exceptions.CommandError(msg)
|
||||||
volume = volume_client_sdk.manage_volume(
|
volume = volume_client.manage_volume(
|
||||||
host=parsed_args.host,
|
host=parsed_args.host,
|
||||||
cluster=parsed_args.cluster,
|
cluster=parsed_args.cluster,
|
||||||
ref=parsed_args.remote_source,
|
ref=parsed_args.remote_source,
|
||||||
@@ -339,35 +385,22 @@ class CreateVolume(command.ShowOne):
|
|||||||
metadata=parsed_args.properties,
|
metadata=parsed_args.properties,
|
||||||
bootable=parsed_args.bootable,
|
bootable=parsed_args.bootable,
|
||||||
)
|
)
|
||||||
data = {}
|
data = _format_volume(volume)
|
||||||
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
|
|
||||||
return zip(*sorted(data.items()))
|
return zip(*sorted(data.items()))
|
||||||
|
|
||||||
source_volume = None
|
source_volume = None
|
||||||
if parsed_args.source:
|
if parsed_args.source:
|
||||||
source_volume_obj = utils.find_resource(
|
source_volume_obj = volume_client.find_volume(
|
||||||
volume_client.volumes, parsed_args.source
|
parsed_args.source, ignore_missing=False
|
||||||
)
|
)
|
||||||
source_volume = source_volume_obj.id
|
source_volume = source_volume_obj.id
|
||||||
size = max(size or 0, source_volume_obj.size)
|
size = max(size or 0, source_volume_obj.size)
|
||||||
|
|
||||||
consistency_group = None
|
consistency_group = None
|
||||||
if parsed_args.consistency_group:
|
if parsed_args.consistency_group:
|
||||||
consistency_group = utils.find_resource(
|
consistency_group = volume_v3.find_consistency_group(
|
||||||
volume_client.consistencygroups, parsed_args.consistency_group
|
volume_client, parsed_args.consistency_group
|
||||||
).id
|
)['id']
|
||||||
|
|
||||||
image = None
|
image = None
|
||||||
if parsed_args.image:
|
if parsed_args.image:
|
||||||
@@ -377,8 +410,8 @@ class CreateVolume(command.ShowOne):
|
|||||||
|
|
||||||
snapshot = None
|
snapshot = None
|
||||||
if parsed_args.snapshot:
|
if parsed_args.snapshot:
|
||||||
snapshot_obj = utils.find_resource(
|
snapshot_obj = volume_client.find_snapshot(
|
||||||
volume_client.volume_snapshots, parsed_args.snapshot
|
parsed_args.snapshot, ignore_missing=False
|
||||||
)
|
)
|
||||||
snapshot = snapshot_obj.id
|
snapshot = snapshot_obj.id
|
||||||
# Cinder requires a value for size when creating a volume
|
# Cinder requires a value for size when creating a volume
|
||||||
@@ -391,14 +424,14 @@ class CreateVolume(command.ShowOne):
|
|||||||
|
|
||||||
backup = None
|
backup = None
|
||||||
if parsed_args.backup:
|
if parsed_args.backup:
|
||||||
backup_obj = utils.find_resource(
|
backup_obj = volume_client.find_backup(
|
||||||
volume_client.backups, parsed_args.backup
|
parsed_args.backup, ignore_missing=False
|
||||||
)
|
)
|
||||||
backup = backup_obj.id
|
backup = backup_obj.id
|
||||||
# As above
|
# As above
|
||||||
size = max(size or 0, backup_obj.size)
|
size = max(size or 0, backup_obj.size)
|
||||||
|
|
||||||
volume = volume_client.volumes.create(
|
volume = volume_client.create_volume(
|
||||||
size=size,
|
size=size,
|
||||||
snapshot_id=snapshot,
|
snapshot_id=snapshot,
|
||||||
name=parsed_args.name,
|
name=parsed_args.name,
|
||||||
@@ -406,9 +439,9 @@ class CreateVolume(command.ShowOne):
|
|||||||
volume_type=parsed_args.type,
|
volume_type=parsed_args.type,
|
||||||
availability_zone=parsed_args.availability_zone,
|
availability_zone=parsed_args.availability_zone,
|
||||||
metadata=parsed_args.properties,
|
metadata=parsed_args.properties,
|
||||||
imageRef=image,
|
image_id=image,
|
||||||
source_volid=source_volume,
|
source_volume_id=source_volume,
|
||||||
consistencygroup_id=consistency_group,
|
consistency_group_id=consistency_group,
|
||||||
scheduler_hints=parsed_args.hint,
|
scheduler_hints=parsed_args.hint,
|
||||||
backup_id=backup,
|
backup_id=backup,
|
||||||
)
|
)
|
||||||
@@ -416,14 +449,14 @@ class CreateVolume(command.ShowOne):
|
|||||||
if parsed_args.bootable is not None:
|
if parsed_args.bootable is not None:
|
||||||
try:
|
try:
|
||||||
if utils.wait_for_status(
|
if utils.wait_for_status(
|
||||||
volume_client.volumes.get,
|
volume_client.get_volume,
|
||||||
volume.id,
|
volume.id,
|
||||||
success_status=['available'],
|
success_status=['available'],
|
||||||
error_status=['error'],
|
error_status=['error'],
|
||||||
sleep_time=1,
|
sleep_time=1,
|
||||||
):
|
):
|
||||||
volume_client.volumes.set_bootable(
|
volume_client.set_volume_bootable_status(
|
||||||
volume.id, parsed_args.bootable
|
volume, parsed_args.bootable
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
msg = _(
|
msg = _(
|
||||||
@@ -436,14 +469,14 @@ class CreateVolume(command.ShowOne):
|
|||||||
if parsed_args.read_only is not None:
|
if parsed_args.read_only is not None:
|
||||||
try:
|
try:
|
||||||
if utils.wait_for_status(
|
if utils.wait_for_status(
|
||||||
volume_client.volumes.get,
|
volume_client.get_volume,
|
||||||
volume.id,
|
volume.id,
|
||||||
success_status=['available'],
|
success_status=['available'],
|
||||||
error_status=['error'],
|
error_status=['error'],
|
||||||
sleep_time=1,
|
sleep_time=1,
|
||||||
):
|
):
|
||||||
volume_client.volumes.update_readonly_flag(
|
volume_client.set_volume_readonly(
|
||||||
volume.id, parsed_args.read_only
|
volume, parsed_args.read_only
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
msg = _(
|
msg = _(
|
||||||
@@ -457,17 +490,8 @@ class CreateVolume(command.ShowOne):
|
|||||||
e,
|
e,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Remove key links from being displayed
|
data = _format_volume(volume)
|
||||||
volume._info.update(
|
return zip(*sorted(data.items()))
|
||||||
{
|
|
||||||
'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()))
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteVolume(command.Command):
|
class DeleteVolume(command.Command):
|
||||||
|
Reference in New Issue
Block a user