diff options
Diffstat (limited to 'openstackclient')
23 files changed, 1208 insertions, 123 deletions
diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index 1be6c765..b67c291f 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -22,7 +22,7 @@ from openstackclient.i18n import _ LOG = logging.getLogger(__name__) -DEFAULT_API_VERSION = '1' +DEFAULT_API_VERSION = '2' API_VERSION_OPTION = 'os_image_api_version' API_NAME = "image" API_VERSIONS = { diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index d9ac8c08..0d54e339 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -38,6 +38,8 @@ from openstackclient.i18n import _ DEFAULT_CONTAINER_FORMAT = 'bare' DEFAULT_DISK_FORMAT = 'raw' +DISK_CHOICES = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", "vhdx", + "vdi", "iso"] LOG = logging.getLogger(__name__) @@ -89,8 +91,9 @@ class CreateImage(command.ShowOne): "--disk-format", default=DEFAULT_DISK_FORMAT, metavar="<disk-format>", - help=_("Image disk format " - "(default: %s)") % DEFAULT_DISK_FORMAT, + choices=DISK_CHOICES, + help=_("Image disk format. The supported options are: %s. " + "The default format is: raw") % ', '.join(DISK_CHOICES) ) parser.add_argument( "--size", @@ -502,14 +505,12 @@ class SetImage(command.Command): container_choices, choices=container_choices ) - disk_choices = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", - "vdi", "iso"] parser.add_argument( "--disk-format", metavar="<disk-format>", - help=_("Disk format of image. Acceptable formats: %s") % - disk_choices, - choices=disk_choices + choices=DISK_CHOICES, + help=_("Image disk format. The supported options are: %s.") % + ', '.join(DISK_CHOICES) ) parser.add_argument( "--size", diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 657277c0..4031952b 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -32,6 +32,8 @@ from openstackclient.identity import common DEFAULT_CONTAINER_FORMAT = 'bare' DEFAULT_DISK_FORMAT = 'raw' +DISK_CHOICES = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", "vhdx", + "vdi", "iso"] LOG = logging.getLogger(__name__) @@ -140,9 +142,10 @@ class CreateImage(command.ShowOne): parser.add_argument( "--disk-format", default=DEFAULT_DISK_FORMAT, + choices=DISK_CHOICES, metavar="<disk-format>", - help=_("Image disk format " - "(default: %s)") % DEFAULT_DISK_FORMAT, + help=_("Image disk format. The supported options are: %s. " + "The default format is: raw") % ', '.join(DISK_CHOICES) ) parser.add_argument( "--min-disk", @@ -650,8 +653,9 @@ class SetImage(command.Command): parser.add_argument( "--disk-format", metavar="<disk-format>", - help=_("Image disk format " - "(default: %s)") % DEFAULT_DISK_FORMAT, + choices=DISK_CHOICES, + help=_("Image disk format. The supported options are: %s") % + ', '.join(DISK_CHOICES) ) protected_group = parser.add_mutually_exclusive_group() protected_group.add_argument( diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 37775e6c..1c06c462 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -99,6 +99,13 @@ def _get_attrs(client_manager, parsed_args): attrs['provider:physical_network'] = parsed_args.physical_network if parsed_args.segmentation_id: attrs['provider:segmentation_id'] = parsed_args.segmentation_id + if parsed_args.qos_policy is not None: + network_client = client_manager.network + _qos_policy = network_client.find_qos_policy(parsed_args.qos_policy, + ignore_missing=False) + attrs['qos_policy_id'] = _qos_policy.id + if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy: + attrs['qos_policy_id'] = None # Update VLAN Transparency for networks if parsed_args.transparent_vlan: attrs['vlan_transparent'] = True @@ -249,6 +256,11 @@ class CreateNetwork(common.NetworkAndComputeShowOne): help=_("Do not use the network as the default external network " "(default)") ) + parser.add_argument( + '--qos-policy', + metavar='<qos-policy>', + help=_("QoS policy to attach to this network (name or ID)") + ) _add_additional_network_options(parser) return parser @@ -572,6 +584,17 @@ class SetNetwork(command.Command): action='store_true', help=_("Do not use the network as the default external network") ) + qos_group = parser.add_mutually_exclusive_group() + qos_group.add_argument( + '--qos-policy', + metavar='<qos-policy>', + help=_("QoS policy to attach to this network (name or ID)") + ) + qos_group.add_argument( + '--no-qos-policy', + action='store_true', + help=_("Remove the QoS policy attached to this network") + ) _add_additional_network_options(parser) return parser diff --git a/openstackclient/network/v2/network_service_provider.py b/openstackclient/network/v2/network_service_provider.py new file mode 100644 index 00000000..3aa33c23 --- /dev/null +++ b/openstackclient/network/v2/network_service_provider.py @@ -0,0 +1,41 @@ +# 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. +# + +"""Network Service Providers Implementation""" + +from osc_lib.command import command +from osc_lib import utils + + +class ListNetworkServiceProvider(command.Lister): + """List Service Providers""" + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + columns = ( + 'service_type', + 'name', + 'is_default', + ) + column_headers = ( + 'Service Type', + 'Name', + 'Default', + ) + + data = client.service_providers() + return(column_headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 64b30c7c..6cae87ee 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -148,6 +148,12 @@ def _get_attrs(client_manager, parsed_args): ).id attrs['tenant_id'] = project_id + if parsed_args.disable_port_security: + attrs['port_security_enabled'] = False + + if parsed_args.enable_port_security: + attrs['port_security_enabled'] = True + return attrs @@ -304,6 +310,17 @@ class CreatePort(command.ShowOne): action='store_true', help=_("Associate no security groups with this port") ) + port_security = parser.add_mutually_exclusive_group() + port_security.add_argument( + '--enable-port-security', + action='store_true', + help=_("Enable port security for this port (Default)") + ) + port_security.add_argument( + '--disable-port-security', + action='store_true', + help=_("Disable port security for this port") + ) return parser @@ -526,6 +543,17 @@ class SetPort(command.Command): action='store_true', help=_("Clear existing security groups associated with this port") ) + port_security = parser.add_mutually_exclusive_group() + port_security.add_argument( + '--enable-port-security', + action='store_true', + help=_("Enable port security for this port") + ) + port_security.add_argument( + '--disable-port-security', + action='store_true', + help=_("Disable port security for this port") + ) return parser diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index eda92948..5366dff6 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -84,6 +84,15 @@ def _get_common_parse_arguments(parser, is_create=True): help=_("DNS server for this subnet " "(repeat option to set multiple DNS servers)") ) + + if not is_create: + parser.add_argument( + '--no-dns-nameservers', + action='store_true', + help=_("Clear existing information of DNS Nameservers. " + "Specify both --dns-nameserver and --no-dns-nameserver " + "to overwrite the current DNS Nameserver information.") + ) parser.add_argument( '--host-route', metavar='destination=<subnet>,gateway=<ip-address>', @@ -532,7 +541,10 @@ class SetSubnet(command.Command): attrs = _get_attrs(self.app.client_manager, parsed_args, is_create=False) if 'dns_nameservers' in attrs: - attrs['dns_nameservers'] += obj.dns_nameservers + if not parsed_args.no_dns_nameservers: + attrs['dns_nameservers'] += obj.dns_nameservers + elif parsed_args.no_dns_nameservers: + attrs['dns_nameservers'] = [] if 'host_routes' in attrs: if not parsed_args.no_host_route: attrs['host_routes'] += obj.host_routes diff --git a/openstackclient/shell.py b/openstackclient/shell.py index be4b5283..e08eee61 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -148,10 +148,10 @@ class OpenStackShell(shell.OpenStackShell): 'auth_type': self._auth_type, }, ) - except (IOError, OSError) as e: + except (IOError, OSError): self.log.critical("Could not read clouds.yaml configuration file") self.print_help_if_requested() - raise e + raise if not self.options.debug: self.options.debug = None diff --git a/openstackclient/tests/functional/network/v2/test_network_service_provider.py b/openstackclient/tests/functional/network/v2/test_network_service_provider.py new file mode 100644 index 00000000..379de430 --- /dev/null +++ b/openstackclient/tests/functional/network/v2/test_network_service_provider.py @@ -0,0 +1,26 @@ +# Copyright (c) 2016, Intel Corporation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from openstackclient.tests.functional import base + + +class TestNetworkServiceProvider(base.TestCase): + """Functional tests for network service provider""" + + SERVICE_TYPE = ['L3_ROUTER_NAT'] + + def test_network_service_provider_list(self): + raw_output = self.openstack('network service provider list') + self.assertIn(self.SERVICE_TYPE, raw_output) diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index aef74f04..036c8336 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -116,7 +116,7 @@ class TestImageCreate(TestImage): self.images_mock.configure_mock(**mock_exception) arglist = [ '--container-format', 'ovf', - '--disk-format', 'fs', + '--disk-format', 'ami', '--min-disk', '10', '--min-ram', '4', '--protected', @@ -126,7 +126,7 @@ class TestImageCreate(TestImage): ] verifylist = [ ('container_format', 'ovf'), - ('disk_format', 'fs'), + ('disk_format', 'ami'), ('min_disk', 10), ('min_ram', 4), ('protected', True), @@ -147,7 +147,7 @@ class TestImageCreate(TestImage): self.images_mock.create.assert_called_with( name=self.new_image.name, container_format='ovf', - disk_format='fs', + disk_format='ami', min_disk=10, min_ram=4, protected=True, diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index ebc9c3a7..2f2212e4 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -130,7 +130,7 @@ class TestImageCreate(TestImage): self.images_mock.configure_mock(**mock_exception) arglist = [ '--container-format', 'ovf', - '--disk-format', 'fs', + '--disk-format', 'ami', '--min-disk', '10', '--min-ram', '4', ('--protected' @@ -143,7 +143,7 @@ class TestImageCreate(TestImage): ] verifylist = [ ('container_format', 'ovf'), - ('disk_format', 'fs'), + ('disk_format', 'ami'), ('min_disk', 10), ('min_ram', 4), ('protected', self.new_image.protected), @@ -165,7 +165,7 @@ class TestImageCreate(TestImage): self.images_mock.create.assert_called_with( name=self.new_image.name, container_format='ovf', - disk_format='fs', + disk_format='ami', min_disk=10, min_ram=4, owner=self.project.id, @@ -193,7 +193,7 @@ class TestImageCreate(TestImage): arglist = [ '--container-format', 'ovf', - '--disk-format', 'fs', + '--disk-format', 'ami', '--min-disk', '10', '--min-ram', '4', '--owner', 'unexist_owner', @@ -203,7 +203,7 @@ class TestImageCreate(TestImage): ] verifylist = [ ('container_format', 'ovf'), - ('disk_format', 'fs'), + ('disk_format', 'ami'), ('min_disk', 10), ('min_ram', 4), ('owner', 'unexist_owner'), @@ -227,7 +227,7 @@ class TestImageCreate(TestImage): arglist = [ '--container-format', 'ovf', - '--disk-format', 'fs', + '--disk-format', 'ami', '--min-disk', '10', '--min-ram', '4', '--protected', @@ -237,7 +237,7 @@ class TestImageCreate(TestImage): ] verifylist = [ ('container_format', 'ovf'), - ('disk_format', 'fs'), + ('disk_format', 'ami'), ('min_disk', 10), ('min_ram', 4), ('protected', True), diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index e96abc09..37f10147 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -299,6 +299,7 @@ class FakeNetwork(object): 'availability_zone_hints': [], 'is_default': False, 'port_security_enabled': True, + 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, } # Overwrite default attributes. @@ -1268,3 +1269,38 @@ class FakeSubnetPool(object): if subnet_pools is None: subnet_pools = FakeSubnetPool.create_subnet_pools(count) return mock.Mock(side_effect=subnet_pools) + + +class FakeNetworkServiceProvider(object): + """Fake Network Service Providers""" + + @staticmethod + def create_one_network_service_provider(attrs=None): + """Create service provider""" + attrs = attrs or {} + + service_provider = { + 'name': 'provider-name-' + uuid.uuid4().hex, + 'service_type': 'service-type-' + uuid.uuid4().hex, + 'default': False, + } + + service_provider.update(attrs) + + provider = fakes.FakeResource( + info=copy.deepcopy(service_provider), + loaded=True) + provider.is_default = service_provider['default'] + + return provider + + @staticmethod + def create_network_service_providers(attrs=None, count=2): + """Create multiple service providers""" + + service_providers = [] + for i in range(0, count): + service_providers.append(FakeNetworkServiceProvider. + create_one_network_service_provider( + attrs)) + return service_providers diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index c0de8640..96b1b102 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -53,6 +53,8 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'availability_zone_hints': ["nova"], } ) + qos_policy = (network_fakes.FakeNetworkQosPolicy. + create_one_qos_policy(attrs={'id': _network.qos_policy_id})) columns = ( 'admin_state_up', @@ -67,6 +69,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'provider_network_type', 'provider_physical_network', 'provider_segmentation_id', + 'qos_policy_id', 'router:external', 'shared', 'status', @@ -86,6 +89,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): _network.provider_network_type, _network.provider_physical_network, _network.provider_segmentation_id, + _network.qos_policy_id, network._format_router_external(_network.is_router_external), _network.shared, _network.status, @@ -102,6 +106,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): self.projects_mock.get.return_value = self.project self.domains_mock.get.return_value = self.domain + self.network.find_qos_policy = mock.Mock(return_value=self.qos_policy) def test_create_no_options(self): arglist = [] @@ -144,6 +149,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): "--provider-network-type", "vlan", "--provider-physical-network", "physnet1", "--provider-segment", "400", + "--qos-policy", self.qos_policy.id, "--transparent-vlan", "--enable-port-security", self._network.name, @@ -160,6 +166,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): ('provider_network_type', 'vlan'), ('physical_network', 'physnet1'), ('segmentation_id', '400'), + ('qos_policy', self.qos_policy.id), ('transparent_vlan', True), ('enable_port_security', True), ('name', self._network.name), @@ -180,6 +187,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'provider:network_type': 'vlan', 'provider:physical_network': 'physnet1', 'provider:segmentation_id': '400', + 'qos_policy_id': self.qos_policy.id, 'vlan_transparent': True, 'port_security_enabled': True, }) @@ -235,6 +243,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): 'provider_network_type', 'provider_physical_network', 'provider_segmentation_id', + 'qos_policy_id', 'router:external', 'shared', 'status', @@ -254,6 +263,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): _network.provider_network_type, _network.provider_physical_network, _network.provider_segmentation_id, + _network.qos_policy_id, network._format_router_external(_network.is_router_external), _network.shared, _network.status, @@ -745,6 +755,8 @@ class TestSetNetwork(TestNetwork): # The network to set. _network = network_fakes.FakeNetwork.create_one_network() + qos_policy = (network_fakes.FakeNetworkQosPolicy. + create_one_qos_policy(attrs={'id': _network.qos_policy_id})) def setUp(self): super(TestSetNetwork, self).setUp() @@ -752,6 +764,7 @@ class TestSetNetwork(TestNetwork): self.network.update_network = mock.Mock(return_value=None) self.network.find_network = mock.Mock(return_value=self._network) + self.network.find_qos_policy = mock.Mock(return_value=self.qos_policy) # Get the command object to test self.cmd = network.SetNetwork(self.app, self.namespace) @@ -770,6 +783,7 @@ class TestSetNetwork(TestNetwork): '--provider-segment', '400', '--no-transparent-vlan', '--enable-port-security', + '--qos-policy', self.qos_policy.name, ] verifylist = [ ('network', self._network.name), @@ -784,6 +798,7 @@ class TestSetNetwork(TestNetwork): ('segmentation_id', '400'), ('no_transparent_vlan', True), ('enable_port_security', True), + ('qos_policy', self.qos_policy.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -801,6 +816,7 @@ class TestSetNetwork(TestNetwork): 'provider:segmentation_id': '400', 'vlan_transparent': False, 'port_security_enabled': True, + 'qos_policy_id': self.qos_policy.id, } self.network.update_network.assert_called_once_with( self._network, **attrs) @@ -813,6 +829,7 @@ class TestSetNetwork(TestNetwork): '--no-share', '--internal', '--disable-port-security', + '--no-qos-policy', ] verifylist = [ ('network', self._network.name), @@ -820,6 +837,7 @@ class TestSetNetwork(TestNetwork): ('no_share', True), ('internal', True), ('disable_port_security', True), + ('no_qos_policy', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -830,6 +848,7 @@ class TestSetNetwork(TestNetwork): 'shared': False, 'router:external': False, 'port_security_enabled': False, + 'qos_policy_id': None, } self.network.update_network.assert_called_once_with( self._network, **attrs) @@ -866,6 +885,7 @@ class TestShowNetwork(TestNetwork): 'provider_network_type', 'provider_physical_network', 'provider_segmentation_id', + 'qos_policy_id', 'router:external', 'shared', 'status', @@ -885,6 +905,7 @@ class TestShowNetwork(TestNetwork): _network.provider_network_type, _network.provider_physical_network, _network.provider_segmentation_id, + _network.qos_policy_id, network._format_router_external(_network.is_router_external), _network.shared, _network.status, diff --git a/openstackclient/tests/unit/network/v2/test_network_service_provider.py b/openstackclient/tests/unit/network/v2/test_network_service_provider.py new file mode 100644 index 00000000..5ba85ddb --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_network_service_provider.py @@ -0,0 +1,71 @@ +# Copyright (c) 2016, Intel Corporation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock + +from openstackclient.network.v2 import network_service_provider \ + as service_provider +from openstackclient.tests.unit.network.v2 import fakes + + +class TestNetworkServiceProvider(fakes.TestNetworkV2): + + def setUp(self): + super(TestNetworkServiceProvider, self).setUp() + self.network = self.app.client_manager.network + + +class TestListNetworkServiceProvider(TestNetworkServiceProvider): + provider_list = \ + fakes.FakeNetworkServiceProvider.create_network_service_providers( + count=2 + ) + + columns = ( + 'Service Type', + 'Name', + 'Default', + ) + + data = [] + + for provider in provider_list: + data.append(( + provider.service_type, + provider.name, + provider.is_default, + )) + + def setUp(self): + super(TestListNetworkServiceProvider, self).setUp() + self.network.service_providers = mock.Mock( + return_value=self.provider_list + ) + + self.cmd = \ + service_provider.ListNetworkServiceProvider(self.app, + self.namespace) + + def test_network_service_provider_list(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.service_providers.assert_called_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index a7a4bf46..9312a897 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -320,6 +320,54 @@ class TestCreatePort(TestPort): self.assertEqual(ref_columns, columns) self.assertEqual(ref_data, data) + def test_create_port_security_enabled(self): + arglist = [ + '--network', self._port.network_id, + '--enable-port-security', + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id,), + ('enable', True), + ('enable_port_security', True), + ('name', 'test-port'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'port_security_enabled': True, + 'name': 'test-port', + }) + + def test_create_port_security_disabled(self): + arglist = [ + '--network', self._port.network_id, + '--disable-port-security', + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id,), + ('enable', True), + ('disable_port_security', True), + ('name', 'test-port'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'port_security_enabled': False, + 'name': 'test-port', + }) + class TestDeletePort(TestPort): @@ -898,6 +946,42 @@ class TestSetPort(TestPort): self.network.update_port.assert_called_once_with(_testport, **attrs) self.assertIsNone(result) + def test_port_security_enabled(self): + arglist = [ + '--enable-port-security', + self._port.id, + ] + verifylist = [ + ('enable_port_security', True), + ('port', self._port.id,) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.network.update_port.assert_called_once_with(self._port, **{ + 'port_security_enabled': True, + }) + + def test_port_security_disabled(self): + arglist = [ + '--disable-port-security', + self._port.id, + ] + verifylist = [ + ('disable_port_security', True), + ('port', self._port.id,) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.network.update_port.assert_called_once_with(self._port, **{ + 'port_security_enabled': False, + }) + class TestShowPort(TestPort): diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 2d51aa4a..e7406575 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -925,13 +925,16 @@ class TestSetSubnet(TestSubnet): {'host_routes': [{'destination': '10.20.20.0/24', 'nexthop': '10.20.20.1'}], 'allocation_pools': [{'start': '8.8.8.200', - 'end': '8.8.8.250'}], }) + 'end': '8.8.8.250'}], + 'dns_nameservers': ["10.0.0.1"], }) self.network.find_subnet = mock.Mock(return_value=_testsubnet) arglist = [ '--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.1', '--no-host-route', '--allocation-pool', 'start=8.8.8.100,end=8.8.8.150', '--no-allocation-pool', + '--dns-nameserver', '10.1.10.1', + '--no-dns-nameservers', _testsubnet.name, ] verifylist = [ @@ -939,6 +942,8 @@ class TestSetSubnet(TestSubnet): "destination": "10.30.30.30/24", "gateway": "10.30.30.1"}]), ('allocation_pools', [{ 'start': '8.8.8.100', 'end': '8.8.8.150'}]), + ('dns_nameservers', ['10.1.10.1']), + ('no_dns_nameservers', True), ('no_host_route', True), ('no_allocation_pool', True), ] @@ -948,6 +953,7 @@ class TestSetSubnet(TestSubnet): 'host_routes': [{ "destination": "10.30.30.30/24", "nexthop": "10.30.30.1"}], 'allocation_pools': [{'start': '8.8.8.100', 'end': '8.8.8.150'}], + 'dns_nameservers': ["10.1.10.1"], } self.network.update_subnet.assert_called_once_with( _testsubnet, **attrs) diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index 434e637a..78a8227e 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -23,102 +23,6 @@ from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.unit import utils -volume_id = 'vvvvvvvv-vvvv-vvvv-vvvvvvvv' -volume_name = 'nigel' -volume_description = 'Nigel Tufnel' -volume_status = 'available' -volume_size = 120 -volume_type = 'to-eleven' -volume_zone = 'stonehenge' -volume_metadata = { - 'Alpha': 'a', - 'Beta': 'b', - 'Gamma': 'g', -} -volume_metadata_str = "Alpha='a', Beta='b', Gamma='g'" - -VOLUME = { - 'id': volume_id, - 'display_name': volume_name, - 'display_description': volume_description, - 'size': volume_size, - 'status': volume_status, - 'attach_status': 'detached', - 'availability_zone': volume_zone, - 'volume_type': volume_type, - 'metadata': volume_metadata, -} - -extension_name = 'SchedulerHints' -extension_namespace = 'http://docs.openstack.org/'\ - 'block-service/ext/scheduler-hints/api/v2' -extension_description = 'Pass arbitrary key/value'\ - 'pairs to the scheduler.' -extension_updated = '2014-02-07T12:00:0-00:00' -extension_alias = 'OS-SCH-HNT' -extension_links = '[{"href":'\ - '"https://github.com/openstack/block-api", "type":'\ - ' "text/html", "rel": "describedby"}]' - -EXTENSION = { - 'name': extension_name, - 'namespace': extension_namespace, - 'description': extension_description, - 'updated': extension_updated, - 'alias': extension_alias, - 'links': extension_links, -} - -type_id = "5520dc9e-6f9b-4378-a719-729911c0f407" -type_name = "fake-lvmdriver-1" - -TYPE = { - 'id': type_id, - 'name': type_name -} - -qos_id = '6f2be1de-997b-4230-b76c-a3633b59e8fb' -qos_consumer = 'front-end' -qos_default_consumer = 'both' -qos_name = "fake-qos-specs" -qos_specs = { - 'foo': 'bar', - 'iops': '9001' -} -qos_association = { - 'association_type': 'volume_type', - 'name': type_name, - 'id': type_id -} - -QOS = { - 'id': qos_id, - 'consumer': qos_consumer, - 'name': qos_name -} - -QOS_DEFAULT_CONSUMER = { - 'id': qos_id, - 'consumer': qos_default_consumer, - 'name': qos_name -} - -QOS_WITH_SPECS = { - 'id': qos_id, - 'consumer': qos_consumer, - 'name': qos_name, - 'specs': qos_specs -} - -QOS_WITH_ASSOCIATIONS = { - 'id': qos_id, - 'consumer': qos_consumer, - 'name': qos_name, - 'specs': qos_specs, - 'associations': [qos_association] -} - - class FakeTransfer(object): """Fake one or more Transfer.""" diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index bd7e5973..41d8e794 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -224,6 +224,8 @@ class FakeVolumeClient(object): self.quota_classes.resource_class = fakes.FakeResource(None, {}) self.consistencygroups = mock.Mock() self.consistencygroups.resource_class = fakes.FakeResource(None, {}) + self.cgsnapshots = mock.Mock() + self.cgsnapshots.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -545,6 +547,82 @@ class FakeConsistencyGroup(object): return consistency_groups +class FakeConsistencyGroupSnapshot(object): + """Fake one or more consistency group snapshot.""" + + @staticmethod + def create_one_consistency_group_snapshot(attrs=None): + """Create a fake consistency group snapshot. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, name, description, etc. + """ + attrs = attrs or {} + + # Set default attributes. + consistency_group_snapshot_info = { + "id": 'id-' + uuid.uuid4().hex, + "name": 'backup-name-' + uuid.uuid4().hex, + "description": 'description-' + uuid.uuid4().hex, + "status": "error", + "consistencygroup_id": 'consistency-group-id' + uuid.uuid4().hex, + "created_at": 'time-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + consistency_group_snapshot_info.update(attrs) + + consistency_group_snapshot = fakes.FakeResource( + info=copy.deepcopy(consistency_group_snapshot_info), + loaded=True) + return consistency_group_snapshot + + @staticmethod + def create_consistency_group_snapshots(attrs=None, count=2): + """Create multiple fake consistency group snapshots. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of consistency group snapshots to fake + :return: + A list of FakeResource objects faking the + consistency group snapshots + """ + consistency_group_snapshots = [] + for i in range(0, count): + consistency_group_snapshot = ( + FakeConsistencyGroupSnapshot. + create_one_consistency_group_snapshot(attrs) + ) + consistency_group_snapshots.append(consistency_group_snapshot) + + return consistency_group_snapshots + + @staticmethod + def get_consistency_group_snapshots(snapshots=None, count=2): + """Get an iterable MagicMock object with a list of faked cgsnapshots. + + If consistenct group snapshots list is provided, then initialize + the Mock object with the list. Otherwise create one. + + :param List snapshots: + A list of FakeResource objects faking consistency group snapshots + :param Integer count: + The number of consistency group snapshots to be faked + :return + An iterable Mock object with side_effect set to a list of faked + consistency groups + """ + if snapshots is None: + snapshots = (FakeConsistencyGroupSnapshot. + create_consistency_group_snapshots(count)) + + return mock.Mock(side_effect=snapshots) + + class FakeExtension(object): """Fake one or more extension.""" diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py index 00e1b60e..e0056421 100644 --- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py @@ -28,6 +28,138 @@ class TestConsistencyGroup(volume_fakes.TestVolume): self.app.client_manager.volume.consistencygroups) self.consistencygroups_mock.reset_mock() + self.types_mock = self.app.client_manager.volume.volume_types + self.types_mock.reset_mock() + + +class TestConsistencyGroupCreate(TestConsistencyGroup): + + volume_type = volume_fakes.FakeType.create_one_type() + new_consistency_group = ( + volume_fakes.FakeConsistencyGroup.create_one_consistency_group()) + + columns = ( + 'availability_zone', + 'created_at', + 'description', + 'id', + 'name', + 'status', + 'volume_types', + ) + data = ( + new_consistency_group.availability_zone, + new_consistency_group.created_at, + new_consistency_group.description, + new_consistency_group.id, + new_consistency_group.name, + new_consistency_group.status, + new_consistency_group.volume_types, + ) + + def setUp(self): + super(TestConsistencyGroupCreate, self).setUp() + self.consistencygroups_mock.create.return_value = ( + self.new_consistency_group) + self.consistencygroups_mock.create_from_src.return_value = ( + self.new_consistency_group) + self.consistencygroups_mock.get.return_value = ( + self.new_consistency_group) + self.types_mock.get.return_value = self.volume_type + + # Get the command object to test + self.cmd = consistency_group.CreateConsistencyGroup(self.app, None) + + def test_consistency_group_create(self): + arglist = [ + '--volume-type', self.volume_type.id, + '--description', self.new_consistency_group.description, + '--availability-zone', + self.new_consistency_group.availability_zone, + self.new_consistency_group.name, + ] + verifylist = [ + ('volume_type', self.volume_type.id), + ('description', self.new_consistency_group.description), + ('availability_zone', + self.new_consistency_group.availability_zone), + ('name', self.new_consistency_group.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.types_mock.get.assert_called_once_with( + self.volume_type.id) + self.consistencygroups_mock.get.assert_not_called() + self.consistencygroups_mock.create.assert_called_once_with( + self.volume_type.id, + name=self.new_consistency_group.name, + description=self.new_consistency_group.description, + availability_zone=self.new_consistency_group.availability_zone, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_consistency_group_create_without_name(self): + arglist = [ + '--volume-type', self.volume_type.id, + '--description', self.new_consistency_group.description, + '--availability-zone', + self.new_consistency_group.availability_zone, + ] + verifylist = [ + ('volume_type', self.volume_type.id), + ('description', self.new_consistency_group.description), + ('availability_zone', + self.new_consistency_group.availability_zone), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.types_mock.get.assert_called_once_with( + self.volume_type.id) + self.consistencygroups_mock.get.assert_not_called() + self.consistencygroups_mock.create.assert_called_once_with( + self.volume_type.id, + name=None, + description=self.new_consistency_group.description, + availability_zone=self.new_consistency_group.availability_zone, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_consistency_group_create_from_source(self): + arglist = [ + '--consistency-group-source', self.new_consistency_group.id, + '--description', self.new_consistency_group.description, + self.new_consistency_group.name, + ] + verifylist = [ + ('consistency_group_source', self.new_consistency_group.id), + ('description', self.new_consistency_group.description), + ('name', self.new_consistency_group.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.types_mock.get.assert_not_called() + self.consistencygroups_mock.get.assert_called_once_with( + self.new_consistency_group.id) + self.consistencygroups_mock.create_from_src.assert_called_with( + None, + self.new_consistency_group.id, + name=self.new_consistency_group.name, + description=self.new_consistency_group.description, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestConsistencyGroupList(TestConsistencyGroup): diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py b/openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py new file mode 100644 index 00000000..3bfe93df --- /dev/null +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py @@ -0,0 +1,351 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from mock import call + +from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes +from openstackclient.volume.v2 import consistency_group_snapshot + + +class TestConsistencyGroupSnapshot(volume_fakes.TestVolume): + + def setUp(self): + super(TestConsistencyGroupSnapshot, self).setUp() + + # Get a shortcut to the TransferManager Mock + self.cgsnapshots_mock = ( + self.app.client_manager.volume.cgsnapshots) + self.cgsnapshots_mock.reset_mock() + self.consistencygroups_mock = ( + self.app.client_manager.volume.consistencygroups) + self.consistencygroups_mock.reset_mock() + + +class TestConsistencyGroupSnapshotCreate(TestConsistencyGroupSnapshot): + + _consistency_group_snapshot = ( + volume_fakes. + FakeConsistencyGroupSnapshot. + create_one_consistency_group_snapshot() + ) + consistency_group = ( + volume_fakes.FakeConsistencyGroup.create_one_consistency_group()) + + columns = ( + 'consistencygroup_id', + 'created_at', + 'description', + 'id', + 'name', + 'status', + ) + data = ( + _consistency_group_snapshot.consistencygroup_id, + _consistency_group_snapshot.created_at, + _consistency_group_snapshot.description, + _consistency_group_snapshot.id, + _consistency_group_snapshot.name, + _consistency_group_snapshot.status, + ) + + def setUp(self): + super(TestConsistencyGroupSnapshotCreate, self).setUp() + self.cgsnapshots_mock.create.return_value = ( + self._consistency_group_snapshot) + self.consistencygroups_mock.get.return_value = ( + self.consistency_group) + + # Get the command object to test + self.cmd = (consistency_group_snapshot. + CreateConsistencyGroupSnapshot(self.app, None)) + + def test_consistency_group_snapshot_create(self): + arglist = [ + '--consistency-group', self.consistency_group.id, + '--description', self._consistency_group_snapshot.description, + self._consistency_group_snapshot.name, + ] + verifylist = [ + ('consistency_group', self.consistency_group.id), + ('description', self._consistency_group_snapshot.description), + ('snapshot_name', self._consistency_group_snapshot.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.consistencygroups_mock.get.assert_called_once_with( + self.consistency_group.id) + self.cgsnapshots_mock.create.assert_called_once_with( + self.consistency_group.id, + name=self._consistency_group_snapshot.name, + description=self._consistency_group_snapshot.description, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_consistency_group_snapshot_create_no_consistency_group(self): + arglist = [ + '--description', self._consistency_group_snapshot.description, + self._consistency_group_snapshot.name, + ] + verifylist = [ + ('description', self._consistency_group_snapshot.description), + ('snapshot_name', self._consistency_group_snapshot.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.consistencygroups_mock.get.assert_called_once_with( + self._consistency_group_snapshot.name) + self.cgsnapshots_mock.create.assert_called_once_with( + self.consistency_group.id, + name=self._consistency_group_snapshot.name, + description=self._consistency_group_snapshot.description, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestConsistencyGroupSnapshotDelete(TestConsistencyGroupSnapshot): + + consistency_group_snapshots = ( + volume_fakes.FakeConsistencyGroupSnapshot. + create_consistency_group_snapshots(count=2) + ) + + def setUp(self): + super(TestConsistencyGroupSnapshotDelete, self).setUp() + + self.cgsnapshots_mock.get = ( + volume_fakes.FakeConsistencyGroupSnapshot. + get_consistency_group_snapshots(self.consistency_group_snapshots) + ) + self.cgsnapshots_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = (consistency_group_snapshot. + DeleteConsistencyGroupSnapshot(self.app, None)) + + def test_consistency_group_snapshot_delete(self): + arglist = [ + self.consistency_group_snapshots[0].id + ] + verifylist = [ + ("consistency_group_snapshot", + [self.consistency_group_snapshots[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.cgsnapshots_mock.delete.assert_called_once_with( + self.consistency_group_snapshots[0].id) + self.assertIsNone(result) + + def test_multiple_consistency_group_snapshots_delete(self): + arglist = [] + for c in self.consistency_group_snapshots: + arglist.append(c.id) + verifylist = [ + ('consistency_group_snapshot', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for c in self.consistency_group_snapshots: + calls.append(call(c.id)) + self.cgsnapshots_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + +class TestConsistencyGroupSnapshotList(TestConsistencyGroupSnapshot): + + consistency_group_snapshots = ( + volume_fakes.FakeConsistencyGroupSnapshot. + create_consistency_group_snapshots(count=2) + ) + consistency_group = ( + volume_fakes.FakeConsistencyGroup.create_one_consistency_group() + ) + + columns = [ + 'ID', + 'Status', + 'Name', + ] + columns_long = [ + 'ID', + 'Status', + 'ConsistencyGroup ID', + 'Name', + 'Description', + 'Created At', + ] + data = [] + for c in consistency_group_snapshots: + data.append(( + c.id, + c.status, + c.name, + )) + data_long = [] + for c in consistency_group_snapshots: + data_long.append(( + c.id, + c.status, + c.consistencygroup_id, + c.name, + c.description, + c.created_at, + )) + + def setUp(self): + super(TestConsistencyGroupSnapshotList, self).setUp() + + self.cgsnapshots_mock.list.return_value = ( + self.consistency_group_snapshots) + self.consistencygroups_mock.get.return_value = self.consistency_group + # Get the command to test + self.cmd = ( + consistency_group_snapshot. + ListConsistencyGroupSnapshot(self.app, None) + ) + + def test_consistency_group_snapshot_list_without_options(self): + arglist = [] + verifylist = [ + ("all_projects", False), + ("long", False), + ("status", None), + ("consistency_group", None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + search_opts = { + 'all_tenants': False, + 'status': None, + 'consistencygroup_id': None, + } + self.cgsnapshots_mock.list.assert_called_once_with( + detailed=True, search_opts=search_opts) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_consistency_group_snapshot_list_with_long(self): + arglist = [ + "--long", + ] + verifylist = [ + ("all_projects", False), + ("long", True), + ("status", None), + ("consistency_group", None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + search_opts = { + 'all_tenants': False, + 'status': None, + 'consistencygroup_id': None, + } + self.cgsnapshots_mock.list.assert_called_once_with( + detailed=True, search_opts=search_opts) + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + + def test_consistency_group_snapshot_list_with_options(self): + arglist = [ + "--all-project", + "--status", self.consistency_group_snapshots[0].status, + "--consistency-group", self.consistency_group.id, + ] + verifylist = [ + ("all_projects", True), + ("long", False), + ("status", self.consistency_group_snapshots[0].status), + ("consistency_group", self.consistency_group.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + search_opts = { + 'all_tenants': True, + 'status': self.consistency_group_snapshots[0].status, + 'consistencygroup_id': self.consistency_group.id, + } + self.consistencygroups_mock.get.assert_called_once_with( + self.consistency_group.id) + self.cgsnapshots_mock.list.assert_called_once_with( + detailed=True, search_opts=search_opts) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestConsistencyGroupSnapshotShow(TestConsistencyGroupSnapshot): + + _consistency_group_snapshot = ( + volume_fakes. + FakeConsistencyGroupSnapshot. + create_one_consistency_group_snapshot() + ) + + columns = ( + 'consistencygroup_id', + 'created_at', + 'description', + 'id', + 'name', + 'status', + ) + data = ( + _consistency_group_snapshot.consistencygroup_id, + _consistency_group_snapshot.created_at, + _consistency_group_snapshot.description, + _consistency_group_snapshot.id, + _consistency_group_snapshot.name, + _consistency_group_snapshot.status, + ) + + def setUp(self): + super(TestConsistencyGroupSnapshotShow, self).setUp() + + self.cgsnapshots_mock.get.return_value = ( + self._consistency_group_snapshot) + self.cmd = (consistency_group_snapshot. + ShowConsistencyGroupSnapshot(self.app, None)) + + def test_consistency_group_snapshot_show(self): + arglist = [ + self._consistency_group_snapshot.id + ] + verifylist = [ + ("consistency_group_snapshot", self._consistency_group_snapshot.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.cgsnapshots_mock.get.assert_called_once_with( + self._consistency_group_snapshot.id) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py index 0754fdc7..9316f287 100644 --- a/openstackclient/volume/v2/consistency_group.py +++ b/openstackclient/volume/v2/consistency_group.py @@ -14,12 +14,89 @@ """Volume v2 consistency group action implementations""" +import logging + from osc_lib.command import command from osc_lib import utils +import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + +class CreateConsistencyGroup(command.ShowOne): + _description = _("Create new consistency group.") + + def get_parser(self, prog_name): + parser = super(CreateConsistencyGroup, self).get_parser(prog_name) + parser.add_argument( + "name", + metavar="<name>", + nargs="?", + help=_("Name of new consistency group (default to None)") + ) + exclusive_group = parser.add_mutually_exclusive_group(required=True) + exclusive_group.add_argument( + "--volume-type", + metavar="<volume-type>", + help=_("Volume type of this consistency group (name or ID)") + ) + exclusive_group.add_argument( + "--consistency-group-source", + metavar="<consistency-group>", + help=_("Existing consistency group (name or ID)") + ) + parser.add_argument( + "--description", + metavar="<description>", + help=_("Description of this consistency group") + ) + parser.add_argument( + "--availability-zone", + metavar="<availability-zone>", + help=_("Availability zone for this consistency group " + "(not available if creating consistency group " + "from source)"), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + if parsed_args.volume_type: + volume_type_id = utils.find_resource( + volume_client.volume_types, + parsed_args.volume_type).id + consistency_group = volume_client.consistencygroups.create( + volume_type_id, + name=parsed_args.name, + description=parsed_args.description, + availability_zone=parsed_args.availability_zone + ) + elif parsed_args.consistency_group_source: + if parsed_args.availability_zone: + msg = _("'--availability-zone' option will not work " + "if creating consistency group from source") + LOG.warning(msg) + consistency_group_id = utils.find_resource( + volume_client.consistencygroups, + parsed_args.consistency_group_source).id + consistency_group_snapshot = None + # TODO(Huanxuan Ao): Support for creating from consistency group + # snapshot after adding "consistency_group_snapshot" resource + consistency_group = ( + volume_client.consistencygroups.create_from_src( + consistency_group_snapshot, + consistency_group_id, + name=parsed_args.name, + description=parsed_args.description + ) + ) + + return zip(*sorted(six.iteritems(consistency_group._info))) + + class ListConsistencyGroup(command.Lister): _description = _("List consistency groups.") diff --git a/openstackclient/volume/v2/consistency_group_snapshot.py b/openstackclient/volume/v2/consistency_group_snapshot.py new file mode 100644 index 00000000..540deb01 --- /dev/null +++ b/openstackclient/volume/v2/consistency_group_snapshot.py @@ -0,0 +1,190 @@ +# +# 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 consistency group snapshot action implementations""" + +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils +import six + +from openstackclient.i18n import _ + + +LOG = logging.getLogger(__name__) + + +class CreateConsistencyGroupSnapshot(command.ShowOne): + _description = _("Create new consistency group snapshot.") + + def get_parser(self, prog_name): + parser = super( + CreateConsistencyGroupSnapshot, self).get_parser(prog_name) + parser.add_argument( + "snapshot_name", + metavar="<snapshot-name>", + nargs="?", + help=_("Name of new consistency group snapshot (default to None)") + ) + parser.add_argument( + "--consistency-group", + metavar="<consistency-group>", + help=_("Consistency group to snapshot (name or ID) " + "(default to be the same as <snapshot-name>)") + ) + parser.add_argument( + "--description", + metavar="<description>", + help=_("Description of this consistency group snapshot") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + consistency_group = parsed_args.consistency_group + if not parsed_args.consistency_group: + # If "--consistency-group" not specified, then consistency_group + # will be the same as the new consistency group snapshot name + consistency_group = parsed_args.snapshot_name + consistency_group_id = utils.find_resource( + volume_client.consistencygroups, + consistency_group).id + consistency_group_snapshot = volume_client.cgsnapshots.create( + consistency_group_id, + name=parsed_args.snapshot_name, + description=parsed_args.description, + ) + + return zip(*sorted(six.iteritems(consistency_group_snapshot._info))) + + +class DeleteConsistencyGroupSnapshot(command.Command): + _description = _("Delete consistency group snapshot(s).") + + def get_parser(self, prog_name): + parser = super( + DeleteConsistencyGroupSnapshot, self).get_parser(prog_name) + parser.add_argument( + "consistency_group_snapshot", + metavar="<consistency-group-snapshot>", + nargs="+", + help=_("Consistency group snapshot(s) to delete (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + result = 0 + + for snapshot in parsed_args.consistency_group_snapshot: + try: + snapshot_id = utils.find_resource(volume_client.cgsnapshots, + snapshot).id + + volume_client.cgsnapshots.delete(snapshot_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete consistency group snapshot " + "with name or ID '%(snapshot)s': %(e)s") + % {'snapshot': snapshot, 'e': e}) + + if result > 0: + total = len(parsed_args.consistency_group_snapshot) + msg = (_("%(result)s of %(total)s consistency group snapshots " + "failed to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListConsistencyGroupSnapshot(command.Lister): + _description = _("List consistency group snapshots.") + + def get_parser(self, prog_name): + parser = super( + ListConsistencyGroupSnapshot, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + action="store_true", + help=_('Show detail for all projects (admin only) ' + '(defaults to False)') + ) + parser.add_argument( + '--long', + action="store_true", + help=_('List additional fields in output') + ) + parser.add_argument( + '--status', + metavar="<status>", + choices=['available', 'error', 'creating', 'deleting', + 'error-deleting'], + help=_('Filters results by a status ("available", "error", ' + '"creating", "deleting" or "error_deleting")') + ) + parser.add_argument( + '--consistency-group', + metavar="<consistency-group>", + help=_('Filters results by a consistency group (name or ID)') + ) + return parser + + def take_action(self, parsed_args): + if parsed_args.long: + columns = ['ID', 'Status', 'ConsistencyGroup ID', + 'Name', 'Description', 'Created At'] + else: + columns = ['ID', 'Status', 'Name'] + volume_client = self.app.client_manager.volume + consistency_group_id = None + if parsed_args.consistency_group: + consistency_group_id = utils.find_resource( + volume_client.consistencygroups, + parsed_args.consistency_group, + ).id + search_opts = { + 'all_tenants': parsed_args.all_projects, + 'status': parsed_args.status, + 'consistencygroup_id': consistency_group_id, + } + consistency_group_snapshots = volume_client.cgsnapshots.list( + detailed=True, + search_opts=search_opts, + ) + + return (columns, ( + utils.get_item_properties( + s, columns) + for s in consistency_group_snapshots)) + + +class ShowConsistencyGroupSnapshot(command.ShowOne): + _description = _("Display consistency group snapshot details") + + def get_parser(self, prog_name): + parser = super( + ShowConsistencyGroupSnapshot, self).get_parser(prog_name) + parser.add_argument( + "consistency_group_snapshot", + metavar="<consistency-group-snapshot>", + help=_("Consistency group snapshot to display (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + consistency_group_snapshot = utils.find_resource( + volume_client.cgsnapshots, + parsed_args.consistency_group_snapshot) + return zip(*sorted(six.iteritems(consistency_group_snapshot._info))) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 0fcfaf7e..d0df677d 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -213,7 +213,7 @@ class SetVolumeType(command.Command): ) parser.add_argument( '--description', - metavar='<name>', + metavar='<description>', help=_('Set volume type description'), ) parser.add_argument( |
